@mounaji_npm/auth
@mounaji_npm/auth
Authentication layer for Mounaji applications. Adapter-driven auth context, React hooks, role-based guards, and a complete ready-made auth UI — sign in, sign up, and forgot password — all in one drop-in package.
Supports Firebase Auth (Google, GitHub, Facebook, Twitter/X, Microsoft, Apple, email/password), NextAuth.js, and JWT/REST backends. New providers can be added by implementing the MounajiAdapter interface.
Interactive Setup (recommended)
# In your project directory — asks which provider, installs deps, writes boilerplate
npx @mounaji_npm/auth setupThe wizard detects your package manager, framework, and TypeScript usage, then installs everything and generates the adapter, provider wrapper, and optional login page.
Install
npm install @mounaji_npm/auth
# Firebase:
npm install firebase
# NextAuth.js:
npm install next-auth
# JWT/REST: no extra depsQuick Start
import { AuthProvider, useAuth } from '@mounaji_npm/auth';
import { createFirebaseAdapter } from '@mounaji_npm/auth/firebase';
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider, GithubAuthProvider } from 'firebase/auth';
const auth = getAuth(initializeApp(firebaseConfig));
const adapter = createFirebaseAdapter({
auth,
providers: {
google: new GoogleAuthProvider(),
github: new GithubAuthProvider(),
},
});
export default function Root() {
return (
<AuthProvider adapter={adapter}>
<App />
</AuthProvider>
);
}
function App() {
const { user, login, logout, loading } = useAuth();
if (loading) return <p>Loading…</p>;
if (!user) return <button onClick={() => login({ method: 'google' })}>Sign in</button>;
return <button onClick={logout}>Sign out ({user.name})</button>;
}Architecture
@mounaji_npm/auth
├── Adapters
│ ├── createFirebaseAdapter (Google, GitHub, Facebook, Twitter/X, Microsoft, Apple + email)
│ └── createJwtAdapter (REST API — login/me/refresh)
├── Context
│ └── AuthProvider — wraps your app, exposes auth state
├── Hooks
│ ├── useAuth — full auth context
│ └── useRole — role-check helpers
├── Guards
│ ├── RoleGate — declarative render guard
│ ├── GuestGate — render only when logged out
│ └── AuthGuard — redirect guard + HOC
└── Components
├── LoginButton — avatar dropdown / sign-in button
├── LoginPage — full auth UI (sign in, sign up, forgot password)
└── PublicNavbar — sticky top nav for marketing pages
Normalized user shape
Regardless of adapter, user is always:
{
id: string,
name: string | null,
email: string | null,
avatar: string | null,
roles: Record<string, boolean>, // { admin: true, editor: true }
raw: any, // original provider object
}Adapters
Firebase (@mounaji_npm/auth/firebase)
Pass a providers map with any combination of Firebase auth providers:
import { createFirebaseAdapter } from '@mounaji_npm/auth/firebase';
import { initializeApp } from 'firebase/app';
import {
getAuth,
GoogleAuthProvider,
GithubAuthProvider,
FacebookAuthProvider,
TwitterAuthProvider,
OAuthProvider,
} from 'firebase/auth';
const auth = getAuth(initializeApp(firebaseConfig));
const adapter = createFirebaseAdapter({
auth,
providers: {
google: new GoogleAuthProvider(),
github: new GithubAuthProvider(),
facebook: new FacebookAuthProvider(),
twitter: new TwitterAuthProvider(),
microsoft: new OAuthProvider('microsoft.com'),
apple: new OAuthProvider('apple.com'),
},
});Registration and password reset are built in when you use the Firebase adapter:
const { register, resetPassword } = useAuth();
// Create a new account
await register({ name: 'Jane Smith', email: 'jane@example.com', password: 'secret123' });
// Send a reset email
await resetPassword('jane@example.com');Roles from Firebase custom claims — set server-side:
// Node.js — Firebase Admin SDK
await admin.auth().setCustomUserClaims(uid, { admin: true, editor: true });They appear on user.roles after the next token refresh.
JWT / REST (createJwtAdapter)
import { createJwtAdapter } from '@mounaji_npm/auth';
const adapter = createJwtAdapter({
loginUrl: '/api/auth/login', // POST { method, email?, password? } → { token, user }
logoutUrl: '/api/auth/logout', // POST
meUrl: '/api/auth/me', // GET → { user, roles }
refreshUrl: '/api/auth/refresh', // POST → { token }
storage: 'localStorage', // 'localStorage' | 'sessionStorage' | 'memory'
tokenKey: 'mn_token',
});NextAuth.js (createNextAuthAdapter)
Full OAuth support for Next.js. See DOCS.md for the complete setup guide.
AuthProvider Props
<AuthProvider
adapter={adapter}
onLogin={(user) => console.log('Logged in', user)}
onLogout={() => console.log('Logged out')}
onAuthReady={(user) => console.log('Auth ready', user)}
>
{children}
</AuthProvider>| Prop | Type | Description |
|---|---|---|
adapter |
MounajiAdapter |
Required. Auth provider adapter. |
onLogin |
(user) => void |
Called after a successful sign-in or registration. |
onLogout |
() => void |
Called after logout. |
onAuthReady |
(user|null) => void |
Called when the initial auth state resolves. |
useAuth
const {
user, // MounajiUser | null
token, // string | null
loading, // boolean
error, // string | null
isAuthenticated, // boolean
login, // async ({ method, email?, password? }) => void
logout, // async () => void
register, // async ({ name?, email, password }) => void — undefined if not supported
resetPassword, // async (email) => void — undefined if not supported
getToken, // async (forceRefresh?) => string | null
hasRole, // (role: string) => boolean
hasAnyRole, // (roles: string[]) => boolean
} = useAuth();register and resetPassword are undefined when the current adapter does not implement them (e.g. JWT adapter, NextAuth). Gate UI on their presence:
const { register, resetPassword } = useAuth();
{register && <a href="/register">Sign up</a>}
{resetPassword && <a href="/forgot">Forgot password?</a>}useRole
const {
isAdmin, // hasRole('admin')
isEditor, // hasRole('editor')
isViewer, // hasRole('viewer')
can, // (role) => boolean
canAny, // (roles[]) => boolean
roles, // Record<string, boolean>
} = useRole();Guards
RoleGate
import { RoleGate, GuestGate } from '@mounaji_npm/auth';
<RoleGate roles="admin"> <AdminPanel /> </RoleGate>
<RoleGate roles={['admin','editor']}> <EditTools /> </RoleGate>
<RoleGate authenticated> <Dashboard /> </RoleGate>
<GuestGate> <MarketingBanner /></GuestGate>AuthGuard
import { AuthGuard, withAuthGuard } from '@mounaji_npm/auth';
<AuthGuard redirectTo="/login" onRedirect={router.push}>
<ProtectedPage />
</AuthGuard>
// HOC variant
export default withAuthGuard(AdminPage, {
redirectTo: '/login',
roles: ['admin'],
onRedirect: (path) => router.push(path),
});Components
LoginPage
A complete, drop-in auth UI. Handles sign-in, sign-up, and forgot-password — all in one component with no routing required.
import { LoginPage } from '@mounaji_npm/auth';
<LoginPage
appName="My App"
tagline="Build something great"
logo={<img src="/logo.svg" />}
isDark={true}
methods={['google', 'github', 'facebook', 'email']}
allowRegister={true}
allowForgotPassword={true}
initialMode="login"
redirectTo="/dashboard"
onRedirect={router.push}
companyName="Acme Inc"
termsUrl="/terms"
privacyUrl="/privacy"
/>| Prop | Type | Default | Description |
|---|---|---|---|
methods |
string[] |
['google'] |
Auth methods to show: 'google' 'github' 'facebook' 'twitter' 'microsoft' 'apple' 'email' |
allowRegister |
boolean |
true |
Show "Sign up" link and registration form. Hidden automatically if adapter has no register(). |
allowForgotPassword |
boolean |
true |
Show "Forgot password?" link. Hidden automatically if adapter has no resetPassword(). |
initialMode |
string |
'login' |
Starting mode: 'login' 'register' 'forgot' |
isDark |
boolean |
true |
Dark or light theme |
appName |
string |
'Mounaji' |
Brand name |
tagline |
string |
'AI Platform' |
Tagline below brand name |
subheadline |
string |
(per mode) | Override the per-mode subheadline |
logo |
ReactNode |
gradient "M" | Logo element |
redirectTo |
string |
'/dashboard' |
Path passed to onRedirect after login |
onRedirect |
(path) => void |
— | Navigation handler |
onSuccess |
(user) => void |
— | Called after successful login/register |
termsUrl |
string |
'/terms' |
Terms of service link |
privacyUrl |
string |
'/privacy' |
Privacy policy link |
companyName |
string |
— | Footer copyright name |
OAuth layout — when ≤ 2 OAuth providers are listed they render full-width ("Continue with Google"). With 3+ providers they collapse into a 2-column compact grid automatically.
LoginButton
Drop-in sign-in button / avatar dropdown. Shows a button when logged out; a user menu when logged in.
import { LoginButton } from '@mounaji_npm/auth';
<LoginButton isDark={true} onNavigate={router.push} />
<LoginButton compact isDark={true} onNavigate={router.push} />| Prop | Default | Description |
|---|---|---|
isDark |
true |
Dark/light theme |
compact |
false |
Icon-only mode |
onNavigate |
— | Navigation handler |
loginLabel |
'Sign in' |
Button label text |
dashboardPath |
'/dashboard' |
Dropdown dashboard link |
settingsPath |
'/settings' |
Dropdown settings link |
PublicNavbar
Sticky top nav for public/marketing pages.
import { PublicNavbar } from '@mounaji_npm/auth';
<PublicNavbar
appName="My App"
isDark={isDark}
onThemeToggle={() => setIsDark(d => !d)}
activePath={currentPath}
onNavigate={router.push}
navItems={[
{ label: 'Home', path: '/' },
{ label: 'Pricing', path: '/pricing' },
]}
loginProps={{ redirectTo: '/dashboard' }}
/>CSS Theming
All components use inline styles driven by CSS variables with the --mn-* prefix. Override them globally to match your brand:
:root {
--mn-color-primary: #3B82F6;
--mn-color-accent: #7C3AED;
--mn-color-bg-dark: #060919;
--mn-color-bg-light: #E5DED2;
--mn-border-dark: rgba(255,255,255,0.07);
--mn-border-light: #C9C2B6;
--mn-text-primary-dark: #F0F4FF;
--mn-text-primary-light: #1C1915;
--mn-text-secondary-dark: #94A3B8;
--mn-text-secondary-light: #47413C;
--mn-font-family: 'Inter', system-ui, sans-serif;
}Wiring with @mounaji_npm/api-client
import { useEffect } from 'react';
import { AuthProvider } from '@mounaji_npm/auth';
import { apiClient } from './api/client.js';
function ApiAuthBridge() {
useEffect(() => {
const unsub = adapter.onAuthChange((user, token) => {
apiClient.setAuthToken(token);
});
apiClient.setTokenRefresher(() => adapter.getToken(true));
return () => unsub?.();
}, []);
return null;
}
export default function Root() {
return (
<AuthProvider adapter={adapter}>
<ApiAuthBridge />
<App />
</AuthProvider>
);
}Full API reference and NextAuth.js setup: DOCS.md