@wmtkrishan/auth
v1.1.1
Published
Reusable authentication package for React applications
Readme
@wmtkrishan/auth
A reusable React authentication package with full login/register flow, route guards, role-based access control, and swappable UI components.
Installation
npm install @wmtkrishan/authPeer Dependencies
npm install axios react react-dom react-router-dom zustandQuick Start
1. Import the stylesheet once in your entry file:
// main.tsx
import '@wmtkrishan/auth/dist/style.css'2. Wrap your app:
import { BrowserRouter } from 'react-router-dom'
import { AuthModule } from '@wmtkrishan/auth'
<BrowserRouter>
<AuthModule
config={{ apiBaseUrl: 'https://api.example.com' }}
routes={{ login: '/login', register: '/register', appHome: '/dashboard' }}
/>
</BrowserRouter>That's it. Default login and register pages are included out of the box.
Config
AuthConfig
| Property | Type | Default | Description |
|---|---|---|---|
| apiBaseUrl | string | — | Required. Base URL for all API calls |
| storage | 'localStorage' \| 'sessionStorage' | 'localStorage' | Where the token is persisted |
| tokenKey | string | 'wmt_auth_token' | Storage key name for the token |
| userKey | string | 'wmt_auth_user' | Storage key name for the user |
| timeout | number | 10000 | Axios request timeout in ms |
| axiosInstance | AxiosInstance | — | Bring your own axios instance |
| tokenResponseKey | string | 'token' | Key path in login/register response that contains the token. Supports dot notation e.g. 'data.accessToken' |
| mapUser | (response: unknown) => AuthUser | identity | Transform the raw getCurrentUser API response into your user shape |
AuthRoutesConfig
| Property | Type | Default | Description |
|---|---|---|---|
| login | string | — | Required. Path for the login page |
| register | string | — | Required. Path for the register page |
| appHome | string | '/dashboard' | Redirect destination after login |
| roleRedirects | Record<string, string> | {} | Permission-based redirects after login |
const routes = {
login: '/login',
register: '/register',
appHome: '/dashboard',
roleRedirects: {
ADMIN: '/admin', // users with ADMIN permission → /admin
EDITOR: '/editor', // users with EDITOR permission → /editor
},
}Patterns
Pattern A — AuthModule (plug-and-play)
<BrowserRouter>
<AuthModule
config={config}
routes={routes}
components={components}
loadingFallback={<Spinner />}
>
<Routes>
<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
</Routes>
</AuthModule>
</BrowserRouter>Pattern B — AuthProvider + AuthRoutes (explicit)
<BrowserRouter>
<AuthProvider config={config} routes={routes}>
<AuthRoutes components={components} loadingFallback={<Spinner />}>
<Routes>
<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
</Routes>
</AuthRoutes>
</AuthProvider>
</BrowserRouter>Both patterns are equivalent. Pattern B gives more control over layout.
Route Guards
ProtectedRoute
Redirects unauthenticated users to the login page. Optionally enforces permissions.
// Any authenticated user
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
// Only users with the ADMIN permission
<ProtectedRoute requiredPermissions={['ADMIN']}>
<AdminPanel />
</ProtectedRoute>
// With a loading fallback
<ProtectedRoute fallback={<Spinner />}>
<Dashboard />
</ProtectedRoute>| Prop | Type | Description |
|---|---|---|
| children | ReactNode | Content to render when access is granted |
| requiredPermissions | string[] | Permissions the user must have. Redirects to appHome if missing |
| fallback | ReactNode | Shown while session is restoring |
GuestRoute
Redirects authenticated users away from public pages (login, register).
<GuestRoute fallback={<Spinner />}>
<LoginPage />
</GuestRoute>Hooks
useAuth()
Returns the current session state.
const { user, token, loading, isAuthenticated } = useAuth()| Return | Type | Description |
|---|---|---|
| user | AuthUser \| null | Current logged-in user |
| token | string \| null | Current auth token |
| loading | boolean | true while session is being restored on mount |
| isAuthenticated | boolean | true when user and token are present |
useLogin()
Handles login, token storage, user fetch, and role-based redirect. Pass any fields your API expects — no fixed shape required.
const { login, loading, error } = useLogin()
// username + password
await login({ username: 'john', password: 'secret' })
// email + password
await login({ email: '[email protected]', password: 'secret' })
// any custom shape
await login({ phone: '+1234567890', pin: '9999', rememberMe: true })| Return | Type | Description |
|---|---|---|
| login | (credentials: LoginCredentials) => Promise<void> | Triggers the login flow |
| loading | boolean | true while request is in-flight |
| error | string \| null | Error message if login failed |
useRegister()
Handles registration, token storage, user fetch, and redirect to appHome.
Pass any fields your API expects.
const { register, loading, error } = useRegister()
await register({ username: 'jane', email: '[email protected]', password: 'secret' })
// with extra fields
await register({ email: '[email protected]', password: 'secret', firstName: 'Jane', referralCode: 'ABC' })| Return | Type | Description |
|---|---|---|
| register | (data: RegisterData) => Promise<void> | Triggers the register flow |
| loading | boolean | true while request is in-flight |
| error | string \| null | Error message if registration failed |
useLogout()
Calls the logout API, clears local state and storage, and redirects to login.
const { logout } = useLogout()
<button onClick={logout}>Logout</button>The local session is always cleared even if the server call fails.
Custom UI
Override any page by passing components to AuthModule or AuthRoutes.
import { NoShell } from '@wmtkrishan/auth'
const components = {
LoginPage: CustomLogin, // your own login page
RegisterPage: CustomRegister, // your own register page
AuthShell: NoShell, // removes the default layout wrapper
}
<AuthModule config={config} routes={routes} components={components} />NoShell
A bare passthrough component. Use it as AuthShell when your custom pages manage their own full-screen layout and you don't want DefaultAuthShell's background or padding applied.
import { NoShell } from '@wmtkrishan/auth'
components={{ AuthShell: NoShell }}Custom Login Example
Use useLogin() inside your component — it handles all logic.
import { useLogin } from '@wmtkrishan/auth'
export function CustomLogin() {
const { login, loading, error } = useLogin()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
return (
<form onSubmit={e => { e.preventDefault(); login({ username, password }) }}>
{error && <p>{error}</p>}
<input value={username} onChange={e => setUsername(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<button disabled={loading}>{loading ? 'Signing in…' : 'Sign in'}</button>
</form>
)
}Custom Register Example
import { useRegister } from '@wmtkrishan/auth'
export function CustomRegister() {
const { register, loading, error } = useRegister()
return (
<form onSubmit={e => {
e.preventDefault()
register({ username: '...', email: '...', password: '...' })
}}>
{error && <p>{error}</p>}
{/* your fields */}
<button disabled={loading}>{loading ? 'Creating…' : 'Register'}</button>
</form>
)
}API Endpoints
The package calls these endpoints against your apiBaseUrl:
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /auth/login | No | Login with credentials |
| POST | /auth/register | No | Register a new user |
| POST | /auth/logout | Yes | Invalidate the session |
| GET | /auth/me | Yes | Fetch the current user |
| POST | /auth/refresh-token | No | Refresh the access token |
Token is sent as Authorization: Bearer <token> on authenticated requests.
TypeScript
All types are exported:
import type {
AuthConfig,
AuthRoutesConfig,
AuthUser,
ComponentOverrides,
LoginCredentials,
RegisterData,
ApiResponse,
} from '@wmtkrishan/auth'AuthUser
Only permissions is typed (required for role-based access control).
All other fields are open — your API response shape is used as-is.
type AuthUser = {
id?: number | string
permissions?: { name: string }[]
[key: string]: unknown // any fields your API returns
}Use mapUser in config to type-cast the user to your own interface:
interface MyUser {
id: number
name: string
email: string
role: string
}
const config = {
apiBaseUrl: '...',
mapUser: (res) => res as MyUser,
}
// Then in components:
const { user } = useAuth()
const myUser = user as MyUserAdvanced — Token Response Key
If your login API returns the token under a different key:
// API returns: { accessToken: 'abc123' }
config = { apiBaseUrl: '...', tokenResponseKey: 'accessToken' }
// API returns: { data: { jwt: 'abc123' } }
config = { apiBaseUrl: '...', tokenResponseKey: 'data.jwt' }Dot notation is supported for nested values.
Advanced — Map User Response
If your getCurrentUser API wraps data in a wrapper object:
// API returns: { status: 'ok', data: { id: 1, name: 'John', role: 'admin' } }
config = {
apiBaseUrl: '...',
mapUser: (res: any) => res.data,
}mapUser runs on every session restore (page reload) and after every login/register.
Advanced — Bring Your Own Axios Instance
import axios from 'axios'
const myAxios = axios.create({ baseURL: 'https://api.example.com' })
<AuthProvider
config={{ apiBaseUrl: 'https://api.example.com', axiosInstance: myAxios }}
routes={routes}
>
...
</AuthProvider>Changelog
1.1.0
LoginCredentialsandRegisterDataare now open records — pass any fields your API expectsAuthUseris now an open type — onlypermissionsis typed, all other fields come from your API- Added
tokenResponseKeyconfig — configure which key (including dot-notation paths) holds the token in login/register responses - Added
mapUserconfig — transform rawgetCurrentUserresponse into your user shape
1.0.4
- Added
README.mdto published package
1.0.3
- Added
NoShellexport for custom full-screen page layouts
1.0.2
- Fixed shell bypass using named
Passthroughcomponent instead ofFragment
1.0.1
- Auto-skip
DefaultAuthShellwhen customLoginPageorRegisterPageis provided
1.0.0
- Initial release
