Now.js Framework Documentation
AuthGuard - Route Protection & Access Control
AuthGuard - Route Protection & Access Control
Documentation for AuthGuard, the Route Protection and Access Control system for the Now.js Framework
📋 Table of Contents
- Overview
- Installation and Import
- Getting Started
- Route Protection
- Role-Based Access Control
- Permission Checking
- Custom Validation
- Guard Results
- Error Handling
- Usage Examples
- API Reference
- Best Practices
- Common Pitfalls
- Performance Considerations
Overview
AuthGuard is an authorization checking system for the Router that protects routes and manages access control based on roles and permissions.
Key Features
- ✅ Route Protection: Protect routes based on authentication
- ✅ Role-Based Access: Check user roles
- ✅ Permission Checking: Fine-grained permission checks
- ✅ Custom Validation: Support for custom validation logic
- ✅ Guest Routes: Routes for guests only
- ✅ Public Routes: Public routes (no auth required)
- ✅ Intended Route Storage: Remember intended route before login
- ✅ Auth State Caching: Cache authentication state
- ✅ Loading Integration: Works with AuthLoadingManager
- ✅ Error Integration: Works with AuthErrorHandler
When to Use AuthGuard
✅ Use AuthGuard when:
- Need to protect routes based on authentication
- Need role-based access control
- Need to check permissions
- Need custom authorization logic
- Using Router with authentication system
❌ Don't use when:
- Not using Router
- Don't need route protection
- Performing manual authorization checks
Installation and Import
AuthGuard is loaded with the Now.js Framework and is ready to use immediately via the window object:
// No import needed - ready to use immediately
console.log(window.AuthGuard); // AuthGuard objectGetting Started
Basic Setup with Router
// Initialize AuthManager first
await AuthManager.init({
enabled: true,
endpoints: {
login: '/api/auth/login',
verify: '/api/auth/verify'
}
});
// Initialize Router with AuthGuard
await Router.init({
routes: [
// Public route
{
path: '/',
handler: 'renderHome',
metadata: { public: true }
},
// Protected route
{
path: '/dashboard',
handler: 'renderDashboard',
metadata: { requiresAuth: true }
}
],
// Enable auth guard
auth: {
enabled: true,
guard: AuthGuard.checkRoute, // Use AuthGuard
defaultRequireAuth: false,
redirects: {
unauthorized: '/login',
forbidden: '/403'
}
}
});
console.log('Router with AuthGuard initialized!');Guard Flow
User navigates to route
↓
Router calls AuthGuard.checkRoute()
↓
┌───────────────────────┐
│ Check Route Type │
│ - Public? │
│ - Guest only? │
│ - Protected? │
└──────────┬────────────┘
↓
┌───────────────────────┐
│ Get Auth State │
│ - Check local state │
│ - Verify with server │
│ (if needed) │
└──────────┬────────────┘
↓
┌───────────────────────┐
│ Check Requirements │
│ - Authentication │
│ - Roles │
│ - Permissions │
│ - Custom validation │
└──────────┬────────────┘
↓
┌────┴────┐
│ Result │
└────┬────┘
↓
┌─────┴─────┐
│ Allowed? │
└─────┬─────┘
Yes │ No
↓ ↓
Navigate Handle Error
(redirect/block)Route Protection
1. Public Routes
// Anyone can access
{
path: '/',
handler: 'renderHome',
metadata: {
public: true // No authentication required
}
}
// Multiple public routes
const publicRoutes = [
{ path: '/', handler: 'home', metadata: { public: true } },
{ path: '/about', handler: 'about', metadata: { public: true } },
{ path: '/contact', handler: 'contact', metadata: { public: true } }
];2. Protected Routes
// Requires authentication
{
path: '/dashboard',
handler: 'renderDashboard',
metadata: {
requiresAuth: true // Must be logged in
}
}
// Protected with redirect
{
path: '/profile',
handler: 'renderProfile',
metadata: {
requiresAuth: true,
redirectUnauthorized: '/login' // Custom redirect
}
}3. Guest-Only Routes
// Only for non-authenticated users
{
path: '/login',
handler: 'renderLogin',
metadata: {
guestOnly: true, // Only for guests
redirectOnAuth: '/dashboard' // Redirect if authenticated
}
}
// Typical guest routes
const guestRoutes = [
{
path: '/login',
handler: 'login',
metadata: {
guestOnly: true,
redirectOnAuth: '/dashboard'
}
},
{
path: '/register',
handler: 'register',
metadata: {
guestOnly: true,
redirectOnAuth: '/dashboard'
}
}
];4. Default Auth Requirement
// Set default for all routes
await Router.init({
routes: [
{ path: '/', handler: 'home' }, // Requires auth (default)
{ path: '/about', handler: 'about' }, // Requires auth (default)
{
path: '/login',
handler: 'login',
metadata: { public: true } // Override: public
}
],
auth: {
enabled: true,
guard: AuthGuard.checkRoute,
defaultRequireAuth: true // All routes require auth by default
}
});Role-Based Access Control
1. Single Role Requirement
// Only admins can access
{
path: '/admin',
handler: 'renderAdmin',
metadata: {
requiresAuth: true,
roles: ['admin'] // Must have admin role
}
}2. Multiple Roles (OR Logic)
// Admin OR moderator can access
{
path: '/moderation',
handler: 'renderModeration',
metadata: {
requiresAuth: true,
roles: ['admin', 'moderator'] // Any of these roles
}
}3. Role Hierarchy
// Define role hierarchy in AuthManager
const user = {
id: 1,
name: 'John Doe',
roles: ['admin'] // Admin has all permissions
};
// Route for moderators and admins
{
path: '/posts/moderate',
handler: 'moderatePosts',
metadata: {
requiresAuth: true,
roles: ['moderator', 'admin']
}
}
// Route for admins only
{
path: '/users/manage',
handler: 'manageUsers',
metadata: {
requiresAuth: true,
roles: ['admin']
}
}4. Dynamic Role Checking
// Check user roles in custom validation
{
path: '/content/:id/edit',
handler: 'editContent',
metadata: {
requiresAuth: true,
validate: async (to, from, router) => {
const user = AuthManager.getUser();
const contentId = to.params.id;
// Fetch content to check ownership
const response = await fetch(`/api/content/${contentId}`);
const content = await response.json();
// Allow if:
// - User is admin
// - User is owner
const isAdmin = user.roles.includes('admin');
const isOwner = content.userId === user.id;
if (!isAdmin && !isOwner) {
return {
allowed: false,
action: 'redirect',
target: '/403',
reason: 'Not owner or admin'
};
}
return { allowed: true };
}
}
}Permission Checking
1. Single Permission
// Requires specific permission
{
path: '/posts/create',
handler: 'createPost',
metadata: {
requiresAuth: true,
permissions: ['write:posts'] // Must have this permission
}
}2. Multiple Permissions (AND Logic)
// Requires ALL permissions
{
path: '/posts/:id/delete',
handler: 'deletePost',
metadata: {
requiresAuth: true,
permissions: ['read:posts', 'delete:posts'] // Must have both
}
}3. Granular Permissions
// Define granular permissions
const routes = [
{
path: '/posts',
handler: 'listPosts',
metadata: {
requiresAuth: true,
permissions: ['read:posts']
}
},
{
path: '/posts/create',
handler: 'createPost',
metadata: {
requiresAuth: true,
permissions: ['write:posts']
}
},
{
path: '/posts/:id/edit',
handler: 'editPost',
metadata: {
requiresAuth: true,
permissions: ['read:posts', 'write:posts']
}
},
{
path: '/posts/:id/delete',
handler: 'deletePost',
metadata: {
requiresAuth: true,
permissions: ['delete:posts']
}
}
];4. Resource-Specific Permissions
// Permission format: action:resource:id
{
path: '/projects/:projectId/edit',
handler: 'editProject',
metadata: {
requiresAuth: true,
validate: async (to, from, router) => {
const user = AuthManager.getUser();
const projectId = to.params.projectId;
// Check if user has permission for this specific project
const hasPermission = user.permissions.some(perm => {
// Check for:
// - write:projects:* (all projects)
// - write:projects:123 (specific project)
return perm === 'write:projects:*' ||
perm === `write:projects:${projectId}`;
});
if (!hasPermission) {
return {
allowed: false,
action: 'redirect',
target: '/403',
reason: 'No permission for this project'
};
}
return { allowed: true };
}
}
}Custom Validation
1. Async Validation
{
path: '/premium/content',
handler: 'premiumContent',
metadata: {
requiresAuth: true,
validate: async (to, from, router) => {
const user = AuthManager.getUser();
// Check subscription status (async)
const response = await fetch('/api/subscription/check');
const { active } = await response.json();
if (!active) {
return {
allowed: false,
action: 'redirect',
target: '/subscribe',
reason: 'Subscription required'
};
}
return { allowed: true };
}
}
}2. Validation with Context
{
path: '/admin/reports/:reportId',
handler: 'viewReport',
metadata: {
requiresAuth: true,
roles: ['admin', 'manager'],
validate: async (to, from, router) => {
const user = AuthManager.getUser();
const reportId = to.params.reportId;
// Managers can only view their department's reports
if (user.roles.includes('manager')) {
const response = await fetch(`/api/reports/${reportId}`);
const report = await response.json();
if (report.department !== user.department) {
return {
allowed: false,
action: 'redirect',
target: '/403',
reason: 'Report not in your department'
};
}
}
// Admins can view all reports
return { allowed: true };
}
}
}3. Validation with Rate Limiting
{
path: '/api/export',
handler: 'exportData',
metadata: {
requiresAuth: true,
validate: async (to, from, router) => {
const user = AuthManager.getUser();
// Check rate limit
const response = await fetch('/api/rate-limit/check', {
method: 'POST',
body: JSON.stringify({ userId: user.id, action: 'export' })
});
const { allowed, remaining, resetAt } = await response.json();
if (!allowed) {
return {
allowed: false,
action: 'block',
reason: `Rate limit exceeded. Try again at ${new Date(resetAt)}`
};
}
return {
allowed: true,
metadata: { remaining } // Pass to handler
};
}
}
}4. Time-Based Access
{
path: '/admin/maintenance',
handler: 'maintenance',
metadata: {
requiresAuth: true,
roles: ['admin'],
validate: async (to, from, router) => {
const now = new Date();
const hour = now.getHours();
// Only accessible during maintenance window (2 AM - 4 AM)
if (hour < 2 || hour >= 4) {
return {
allowed: false,
action: 'block',
reason: 'Maintenance mode only accessible between 2 AM - 4 AM'
};
}
return { allowed: true };
}
}
}Guard Results
Result Object Structure
// Allowed result
{
allowed: true,
reason: 'All checks passed',
metadata: {} // Optional additional data
}
// Denied result
{
allowed: false,
action: 'redirect', // 'redirect', 'block', 'render'
target: '/login', // Redirect/render target
reason: 'Not authenticated',
error: Error // Optional error object
}Action Types
1. Redirect Action
// Redirect to another route
{
allowed: false,
action: 'redirect',
target: '/login',
reason: 'Authentication required'
}2. Block Action
// Block navigation (stay on current page)
{
allowed: false,
action: 'block',
reason: 'Insufficient permissions'
}3. Render Action
// Render error page
{
allowed: false,
action: 'render',
target: '/403',
reason: 'Forbidden'
}Error Handling
1. Integration with AuthErrorHandler
// AuthGuard automatically uses AuthErrorHandler
// when guard check fails
// Guard failure triggers error handler
const guardResult = await AuthGuard.checkRoute(to, from, router);
if (!guardResult.allowed) {
// AuthErrorHandler processes the failure
await AuthGuard.handleGuardFailure(guardResult, route, options, router);
}2. Custom Error Handling
// Listen to guard errors
document.addEventListener('auth:guard:failed', (e) => {
const { route, reason, action } = e.detail;
console.log('Guard failed for route:', route.path);
console.log('Reason:', reason);
console.log('Action:', action);
// Custom handling
if (reason.includes('subscription')) {
showSubscriptionModal();
}
});3. Error Types
// Different error types from guard failures
// Authentication error
{
allowed: false,
action: 'redirect',
target: '/login',
reason: 'Not authenticated',
errorType: 'UNAUTHORIZED'
}
// Authorization error
{
allowed: false,
action: 'redirect',
target: '/403',
reason: 'Missing required role: admin',
errorType: 'FORBIDDEN'
}
// Custom validation error
{
allowed: false,
action: 'block',
reason: 'Subscription expired',
errorType: 'CUSTOM_VALIDATION'
}Usage Examples
1. Complete Router Setup with AuthGuard
// Initialize authentication system
await AuthManager.init({
enabled: true,
endpoints: {
login: '/api/auth/login',
logout: '/api/auth/logout',
verify: '/api/auth/verify'
},
redirects: {
afterLogin: '/dashboard',
afterLogout: '/login'
}
});
// Define routes with protection
await Router.init({
routes: [
// Public routes
{
path: '/',
handler: 'renderHome',
metadata: { public: true }
},
{
path: '/about',
handler: 'renderAbout',
metadata: { public: true }
},
// Guest-only routes
{
path: '/login',
handler: 'renderLogin',
metadata: {
guestOnly: true,
redirectOnAuth: '/dashboard'
}
},
{
path: '/register',
handler: 'renderRegister',
metadata: {
guestOnly: true,
redirectOnAuth: '/dashboard'
}
},
// Protected routes
{
path: '/dashboard',
handler: 'renderDashboard',
metadata: {
requiresAuth: true
}
},
{
path: '/profile',
handler: 'renderProfile',
metadata: {
requiresAuth: true
}
},
// Role-based routes
{
path: '/admin',
handler: 'renderAdmin',
metadata: {
requiresAuth: true,
roles: ['admin']
}
},
{
path: '/moderation',
handler: 'renderModeration',
metadata: {
requiresAuth: true,
roles: ['admin', 'moderator']
}
},
// Permission-based routes
{
path: '/posts/create',
handler: 'createPost',
metadata: {
requiresAuth: true,
permissions: ['write:posts']
}
},
{
path: '/posts/:id/delete',
handler: 'deletePost',
metadata: {
requiresAuth: true,
permissions: ['delete:posts']
}
}
],
// Enable AuthGuard
auth: {
enabled: true,
guard: AuthGuard.checkRoute,
defaultRequireAuth: false,
redirects: {
unauthorized: '/login',
forbidden: '/403'
},
skipPatterns: [
'/api/*',
'/assets/*',
'*.js',
'*.css'
]
}
});
console.log('Router with AuthGuard initialized!');2. Role-Based Dashboard
// Define routes based on roles
const routes = [
// User dashboard
{
path: '/dashboard/user',
handler: async () => {
const user = AuthManager.getUser();
return `
<div>
<h1>User Dashboard</h1>
<p>Welcome, ${user.name}!</p>
</div>
`;
},
metadata: {
requiresAuth: true,
roles: ['user', 'moderator', 'admin']
}
},
// Moderator dashboard
{
path: '/dashboard/moderator',
handler: async () => {
return `
<div>
<h1>Moderator Dashboard</h1>
<div id="modTools">...</div>
</div>
`;
},
metadata: {
requiresAuth: true,
roles: ['moderator', 'admin']
}
},
// Admin dashboard
{
path: '/dashboard/admin',
handler: async () => {
return `
<div>
<h1>Admin Dashboard</h1>
<div id="adminPanel">...</div>
</div>
`;
},
metadata: {
requiresAuth: true,
roles: ['admin']
}
}
];
// Navigate based on user role
async function goToDashboard() {
const user = AuthManager.getUser();
if (user.roles.includes('admin')) {
await Router.navigate('/dashboard/admin');
} else if (user.roles.includes('moderator')) {
await Router.navigate('/dashboard/moderator');
} else {
await Router.navigate('/dashboard/user');
}
}3. Content Ownership Validation
{
path: '/posts/:id/edit',
handler: 'editPost',
metadata: {
requiresAuth: true,
validate: async (to, from, router) => {
const user = AuthManager.getUser();
const postId = to.params.id;
// Fetch post data
const response = await fetch(`/api/posts/${postId}`);
if (!response.ok) {
return {
allowed: false,
action: 'redirect',
target: '/404',
reason: 'Post not found'
};
}
const post = await response.json();
// Check ownership or admin role
const isOwner = post.userId === user.id;
const isAdmin = user.roles.includes('admin');
if (!isOwner && !isAdmin) {
return {
allowed: false,
action: 'redirect',
target: '/403',
reason: 'You can only edit your own posts'
};
}
return {
allowed: true,
metadata: { post } // Pass post data to handler
};
}
}
}4. Subscription-Based Access
{
path: '/premium/*',
handler: 'premiumContent',
metadata: {
requiresAuth: true,
validate: async (to, from, router) => {
const user = AuthManager.getUser();
// Check subscription
const response = await fetch('/api/subscription/status');
const subscription = await response.json();
if (!subscription || subscription.status !== 'active') {
return {
allowed: false,
action: 'redirect',
target: '/subscribe',
reason: 'Active subscription required'
};
}
// Check subscription level for specific content
const requiredLevel = 'premium';
if (subscription.level !== requiredLevel) {
return {
allowed: false,
action: 'redirect',
target: '/upgrade',
reason: `${requiredLevel} subscription required`
};
}
return {
allowed: true,
metadata: { subscription }
};
}
}
}5. Multi-Factor Authentication Check
{
path: '/settings/security',
handler: 'securitySettings',
metadata: {
requiresAuth: true,
validate: async (to, from, router) => {
const user = AuthManager.getUser();
// Check if MFA is required
if (user.mfaEnabled && !user.mfaVerified) {
// Store intended route
AuthGuard.storeIntendedRoute(to, router);
return {
allowed: false,
action: 'redirect',
target: '/mfa/verify',
reason: 'MFA verification required'
};
}
return { allowed: true };
}
}
}6. IP Whitelist Validation
{
path: '/admin/sensitive',
handler: 'sensitivePage',
metadata: {
requiresAuth: true,
roles: ['admin'],
validate: async (to, from, router) => {
// Check IP whitelist
const response = await fetch('/api/security/check-ip');
const { allowed, ip } = await response.json();
if (!allowed) {
// Log security event
await fetch('/api/security/log', {
method: 'POST',
body: JSON.stringify({
event: 'UNAUTHORIZED_IP_ACCESS',
ip,
path: to.path,
userId: AuthManager.getUser().id
})
});
return {
allowed: false,
action: 'block',
reason: 'Access from this IP address is not allowed'
};
}
return { allowed: true };
}
}
}API Reference
Methods
checkRoute(to, from, router)
Main guard function - checks authorization before navigation
Parameters:
to(Object) - Route being navigated tofrom(Object) - Current routerouter(Object) - Router instance
Returns: Promise<Object> - Guard result
Example:
const result = await AuthGuard.checkRoute(toRoute, fromRoute, routerInstance);
if (result.allowed) {
console.log('Navigation allowed');
} else {
console.log('Navigation denied:', result.reason);
}getAuthState(authManager)
Get current authentication state with server verification
Parameters:
authManager(Object) - AuthManager instance
Returns: Promise<Object> - Auth state
Example:
const authState = await AuthGuard.getAuthState(AuthManager);
console.log('Authenticated:', authState.isAuthenticated);
console.log('User:', authState.user);checkRoles(requiredRoles, userRoles)
Check if user has required roles
Parameters:
requiredRoles(Array) - Required rolesuserRoles(Array) - User's roles
Returns: boolean - True if user has any required role
Example:
const hasRole = AuthGuard.checkRoles(['admin', 'moderator'], user.roles);checkPermissions(requiredPermissions, userPermissions)
Check if user has required permissions
Parameters:
requiredPermissions(Array) - Required permissionsuserPermissions(Array) - User's permissions
Returns: boolean - True if user has all required permissions
Example:
const hasPerms = AuthGuard.checkPermissions(
['read:posts', 'write:posts'],
user.permissions
);storeIntendedRoute(route, router)
Store intended route for redirect after login
Parameters:
route(Object) - Route objectrouter(Object) - Router instance
Returns: void
Example:
AuthGuard.storeIntendedRoute(toRoute, router);getIntendedRoute()
Get and clear stored intended route
Returns: string|null - Route path or null
Example:
const intendedRoute = AuthGuard.getIntendedRoute();
if (intendedRoute) {
Router.navigate(intendedRoute);
}clearCache()
Clear authentication state cache
Returns: void
Example:
AuthGuard.clearCache();Best Practices
1. Always Set Public Routes Explicitly
// ✅ Good - explicit public routes
{
path: '/',
handler: 'home',
metadata: { public: true }
}
// ❌ Bad - ambiguous protection
{
path: '/',
handler: 'home'
// Is this protected or public?
}2. Use Role Hierarchy
// ✅ Good - clear role hierarchy
const routes = [
{
path: '/content/view',
metadata: { roles: ['user', 'moderator', 'admin'] }
},
{
path: '/content/moderate',
metadata: { roles: ['moderator', 'admin'] }
},
{
path: '/content/admin',
metadata: { roles: ['admin'] }
}
];
// ❌ Bad - unclear hierarchy
{
path: '/admin',
metadata: { roles: ['user'] } // Users shouldn't access admin!
}3. Combine Roles and Custom Validation
// ✅ Good - roles + validation
{
path: '/posts/:id/edit',
metadata: {
requiresAuth: true,
roles: ['admin', 'author'],
validate: async (to) => {
// Authors can only edit their own posts
const user = AuthManager.getUser();
if (user.roles.includes('author')) {
const post = await fetchPost(to.params.id);
return {
allowed: post.authorId === user.id,
reason: 'Can only edit your own posts'
};
}
// Admins can edit any post
return { allowed: true };
}
}
}4. Cache Auth State Appropriately
// ✅ Good - periodic verification
await Router.init({
auth: {
enabled: true,
guard: AuthGuard.checkRoute,
verifyInterval: 5 * 60 * 1000 // Verify every 5 minutes
}
});
// ❌ Bad - verify on every navigation
// This happens automatically if not cached5. Handle Guard Failures Gracefully
// ✅ Good - user-friendly messages
document.addEventListener('auth:guard:failed', (e) => {
const { reason, action } = e.detail;
if (action === 'redirect') {
showNotification(`Redirecting: ${reason}`, 'info');
} else {
showNotification(reason, 'error');
}
});
// ❌ Bad - silent failures
// User doesn't know why navigation failedCommon Pitfalls
1. Forgetting to Set requiresAuth
// ❌ Bad - no auth requirement specified
{
path: '/dashboard',
handler: 'dashboard',
metadata: {
roles: ['user'] // Roles won't be checked without requiresAuth!
}
}
// ✅ Good - explicit auth requirement
{
path: '/dashboard',
handler: 'dashboard',
metadata: {
requiresAuth: true,
roles: ['user']
}
}2. Confusing Roles and Permissions
// ❌ Bad - mixing concepts
{
path: '/posts/delete',
metadata: {
roles: ['delete:posts'] // This is a permission, not a role!
}
}
// ✅ Good - correct usage
{
path: '/posts/delete',
metadata: {
requiresAuth: true,
permissions: ['delete:posts'] // Correct
}
}3. Not Handling Custom Validation Errors
// ❌ Bad - no error handling in validation
{
metadata: {
validate: async (to) => {
const data = await fetch('/api/check'); // May throw!
return { allowed: data.ok };
}
}
}
// ✅ Good - handle errors
{
metadata: {
validate: async (to) => {
try {
const response = await fetch('/api/check');
const data = await response.json();
return { allowed: data.ok };
} catch (error) {
console.error('Validation error:', error);
return {
allowed: false,
reason: 'Validation failed'
};
}
}
}
}4. Circular Redirects
// ❌ Bad - can cause infinite loop
{
path: '/login',
metadata: {
requiresAuth: true // Redirects to /login if not authenticated!
}
}
// ✅ Good - login is guest-only
{
path: '/login',
metadata: {
guestOnly: true,
redirectOnAuth: '/dashboard'
}
}Performance Considerations
1. Cache Auth State
// ✅ AuthGuard caches auth state automatically
// Subsequent checks reuse cached state
await AuthGuard.checkRoute(route1, null, router);
await AuthGuard.checkRoute(route2, null, router); // Uses cache
// Manual cache clearing
AuthGuard.clearCache(); // Force re-verification2. Minimize Custom Validation API Calls
// ❌ Bad - API call on every navigation
{
metadata: {
validate: async (to) => {
const response = await fetch('/api/check-subscription');
return { allowed: response.ok };
}
}
}
// ✅ Good - cache validation results
let subscriptionCache = null;
let cacheTime = 0;
{
metadata: {
validate: async (to) => {
const now = Date.now();
if (!subscriptionCache || (now - cacheTime) > 60000) {
const response = await fetch('/api/check-subscription');
subscriptionCache = await response.json();
cacheTime = now;
}
return { allowed: subscriptionCache.active };
}
}
}3. Optimize Role/Permission Checks
// ✅ AuthGuard optimizes checks internally
// - Short-circuits on first match (OR logic for roles)
// - Caches user roles/permissionsRelated Documentation
- Authentication.md - Authentication system overview
- AuthManager.md - Core authentication manager
- TokenService.md - JWT token management
- AuthErrorHandler.md - Error handling
- AuthLoadingManager.md - Loading states
- Router.md - Routing system