Now.js Framework Documentation

Now.js Framework Documentation

AuthGuard - Route Protection & Access Control

EN 31 Oct 2025 01:27

AuthGuard - Route Protection & Access Control

Documentation for AuthGuard, the Route Protection and Access Control system for the Now.js Framework

📋 Table of Contents

  1. Overview
  2. Installation and Import
  3. Getting Started
  4. Route Protection
  5. Role-Based Access Control
  6. Permission Checking
  7. Custom Validation
  8. Guard Results
  9. Error Handling
  10. Usage Examples
  11. API Reference
  12. Best Practices
  13. Common Pitfalls
  14. 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 object

Getting 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 to
  • from (Object) - Current route
  • router (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 roles
  • userRoles (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 permissions
  • userPermissions (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 object
  • router (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 cached

5. 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 failed

Common 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-verification

2. 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/permissions