Now.js Framework Documentation

Now.js Framework Documentation

AuthManager - Core Authentication Manager

EN 31 Oct 2025 01:16

AuthManager - Core Authentication Manager

Documentation for AuthManager, the core Authentication Manager of the Now.js Framework

📋 Table of Contents

  1. Overview
  2. Installation & Import
  3. Getting Started
  4. Configuration
  5. Authentication Methods
  6. Token Management
  7. Session Management
  8. Security Features
  9. Events System
  10. Usage Examples
  11. API Reference
  12. Best Practices
  13. Common Pitfalls
  14. Performance Considerations
  15. Security Considerations

Overview

AuthManager is the core authentication manager that handles the entire authentication lifecycle: login, logout, token management, session handling, and security features.

Key Features

  • Login/Logout: Full login and logout flows
  • Token Management: Manages JWT tokens (access & refresh)
  • Auto Token Refresh: Automatically refresh tokens before expiry
  • Multiple Auth Types: Bearer, Basic, OAuth, Hybrid authentication
  • HttpOnly Cookies: Secure storage for tokens via httpOnly cookies
  • CSRF Protection: Automatic CSRF protection
  • Rate Limiting: Limits login attempts
  • Session State: Manages user state and sessions
  • Social Login: Supports OAuth social providers
  • HTTP Integration: Integrates automatically with HttpClient
  • Event System: Emits events for lifecycle hooks
  • Error Handling: Automatic error handling
  • Loading States: Works with AuthLoadingManager

When to Use AuthManager

✅ Use AuthManager when:

  • Your application needs user authentication
  • You require JWT token management and automatic token refresh
  • You need CSRF protection
  • You want social login
  • You need role-based access control

❌ Do not use when:

  • The application does not need authentication
  • You prefer extreme simplicity (may use basic auth instead)
  • You already use another authentication system

Installation & Import

AuthManager ships with the Now.js Framework:

<!-- Load dependencies first -->
<script src="/Now/js/TokenService.js"></script>
<script src="/Now/js/AuthLoadingManager.js"></script>
<script src="/Now/js/AuthErrorHandler.js"></script>

<!-- Load AuthManager -->
<script src="/Now/js/AuthManager.js"></script>

After loading, AuthManager is available via:

  • window.AuthManager - Global access
  • Now.getManager('auth') - Through the Now manager system
// Access via window
console.log(window.AuthManager); // AuthManager object

// Access via Now
const authManager = Now.getManager('auth');
console.log(authManager); // AuthManager object

Getting Started

Basic Initialization

// Initialize with default config
await AuthManager.init();

console.log('AuthManager initialized!');
console.log('Authenticated:', AuthManager.isAuthenticated());

Initialization with Config

await AuthManager.init({
  enabled: true,
  type: 'jwt-httponly',
  autoInit: true,

  endpoints: {
    login: '/api/v1/auth/login',
    logout: '/api/v1/auth/logout',
    verify: '/api/v1/auth/verify',
    refresh: '/api/v1/auth/refresh',
    me: '/api/v1/auth/me'
  },

  security: {
    csrf: true,
    autoRefresh: true,
    rateLimiting: true,
    maxLoginAttempts: 5
  },

  redirects: {
    afterLogin: '/dashboard',
    afterLogout: '/login',
    unauthorized: '/login'
  },

  token: {
    cookieName: 'auth_token',
    refreshCookieName: 'refresh_token',
    storageKey: 'auth_user'
  }
});

console.log('AuthManager initialized with custom config!');

Check Initialization Status

// Check if AuthManager is initialized
if (AuthManager.isInitialized()) {
  console.log('AuthManager is ready');
} else {
  console.log('AuthManager not initialized yet');
  await AuthManager.init();
}

Configuration

AuthManager has several configuration sections you can customize.

1. General Settings

{
  enabled: false,           // Enable/disable authentication
  type: 'jwt-httponly',     // Auth type: jwt-httponly, bearer, basic, oauth, hybrid
  autoInit: true            // Auto-initialize on load
}

Auth Types:

Type Description Use Case
jwt-httponly JWT stored in httpOnly cookies Recommended - Most secure
bearer JWT in Authorization header API clients, mobile apps
basic Basic authentication Simple auth, legacy systems
oauth OAuth 2.0 flow Social login, third-party auth
hybrid Mix of multiple types Complex scenarios

Example:

// Default - JWT in httpOnly cookies (recommended)
await AuthManager.init({
  type: 'jwt-httponly'
});

// Bearer token (for APIs)
await AuthManager.init({
  type: 'bearer'
});

// Hybrid (support multiple types)
await AuthManager.init({
  type: 'hybrid'
});

2. Endpoints Configuration

{
  endpoints: {
    login: 'api/v1/auth/login',           // Login endpoint
    logout: 'api/v1/auth/logout',         // Logout endpoint
    verify: 'api/v1/auth/verify',         // Verify token endpoint
    refresh: 'api/v1/auth/refresh',       // Refresh token endpoint
    me: 'api/v1/auth/me',                 // Get current user endpoint
    social: 'api/v1/auth/social/{provider}',  // Social login endpoint
    callback: 'api/v1/auth/callback'      // OAuth callback endpoint
  }
}

Endpoints Details:

1. Login Endpoint

// POST /api/v1/auth/login
// Request:
{
  "username": "user@example.com",
  "password": "secret123"
}

// Response:
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1...",      // Access token
  "refresh_token": "eyJhbGciOiJIUz...",  // Refresh token (optional)
  "user": {
    "id": 1,
    "name": "John Doe",
    "email": "user@example.com",
    "roles": ["user"],
    "permissions": ["read:posts"]
  }
}

2. Logout Endpoint

// POST /api/v1/auth/logout
// Request: (no body required)
// Headers:
{
  "Authorization": "Bearer eyJhbGciOiJIUzI1...",
  "X-CSRF-Token": "csrf-token-here"
}

// Response:
{
  "success": true,
  "message": "Logged out successfully"
}

3. Verify Endpoint

// GET /api/v1/auth/verify
// Headers:
{
  "Authorization": "Bearer eyJhbGciOiJIUzI1..."
}

// Response:
{
  "valid": true,
  "user": {
    "id": 1,
    "name": "John Doe"
  }
}

4. Refresh Endpoint

// POST /api/v1/auth/refresh
// Request:
{
  "refresh_token": "eyJhbGciOiJIUzI1..."  // Or sent as httpOnly cookie
}

// Response:
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1...",  // New access token
  "expires_in": 900                 // Token lifetime in seconds
}

5. Me Endpoint

// GET /api/v1/auth/me
// Headers:
{
  "Authorization": "Bearer eyJhbGciOiJIUzI1..."
}

// Response:
{
  "id": 1,
  "name": "John Doe",
  "email": "user@example.com",
  "roles": ["user", "admin"],
  "permissions": ["read:posts", "write:posts"]
}

6. Social Login Endpoint

// GET /api/v1/auth/social/google
// Query params:
{
  "redirect_uri": "http://localhost/auth/callback"
}

// Response: Redirect to OAuth provider
// Or return authorization URL
{
  "authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?..."
}

7. Callback Endpoint

// GET /api/v1/auth/callback
// Query params:
{
  "code": "authorization_code",
  "state": "random_state",
  "provider": "google"
}

// Response:
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1...",
  "user": {
    "id": 1,
    "name": "John Doe",
    "email": "user@example.com"
  }
}

Customize Endpoints:

await AuthManager.init({
  endpoints: {
    login: '/auth/signin',          // Custom login path
    logout: '/auth/signout',        // Custom logout path
    verify: '/auth/check',          // Custom verify path
    refresh: '/auth/token/refresh', // Custom refresh path
    me: '/auth/user/profile'        // Custom user profile path
  }
});

3. Security Settings

{
  security: {
    // CSRF Protection
    csrf: true,                          // Enable CSRF protection
    csrfIncludeSafeMethods: true,        // Include CSRF token in GET/HEAD

    // Rate Limiting
    rateLimiting: true,                  // Enable rate limiting
    maxLoginAttempts: 5,                 // Max login attempts before lockout
    lockoutTime: 30 * 60 * 1000,        // Lockout time (30 minutes)

    // Auto Refresh
    autoRefresh: true,                   // Auto-refresh tokens
    refreshBeforeExpiry: 5 * 60 * 1000  // Refresh 5 minutes before expiry
  }
}

CSRF Protection:

// Enable CSRF protection
await AuthManager.init({
  security: {
    csrf: true,
    csrfIncludeSafeMethods: true  // Include in GET/HEAD requests too
  }
});

// CSRF token will be automatically:
// 1. Read from <meta name="csrf-token"> tag
// 2. Included in X-CSRF-Token header
// 3. Validated by server

Rate Limiting:

// Limit login attempts
await AuthManager.init({
  security: {
    rateLimiting: true,
    maxLoginAttempts: 5,           // Allow 5 attempts
    lockoutTime: 30 * 60 * 1000   // Lock for 30 minutes
  }
});

// Check rate limit manually
const canLogin = AuthManager.checkRateLimit('user@example.com');
if (!canLogin) {
  console.log('Too many login attempts. Please try again later.');
}

Auto Refresh:

// Auto-refresh tokens before expiry
await AuthManager.init({
  security: {
    autoRefresh: true,
    refreshBeforeExpiry: 5 * 60 * 1000  // Refresh 5 min before expiry
  }
});

// Manual refresh
const refreshed = await AuthManager.refreshToken();
if (refreshed) {
  console.log('Token refreshed successfully');
}

// Disable auto refresh
await AuthManager.init({
  security: {
    autoRefresh: false
  }
});

4. Redirects Configuration

{
  redirects: {
    afterLogin: '/',            // Redirect after successful login
    afterLogout: '/login',      // Redirect after logout
    unauthorized: '/login',     // Redirect when unauthorized
    forbidden: '/403'           // Redirect when forbidden
  }
}

Example:

await AuthManager.init({
  redirects: {
    afterLogin: '/dashboard',     // Go to dashboard after login
    afterLogout: '/login',        // Go to login page after logout
    unauthorized: '/login',       // Go to login if not authenticated
    forbidden: '/403'             // Go to 403 page if no permission
  }
});

// Redirect manually
AuthManager.redirectTo('/dashboard');

// Save intended URL
AuthManager.saveIntendedUrl('/admin/settings');

// After login, redirect to intended URL
const intendedUrl = AuthManager.getIntendedUrl();
if (intendedUrl) {
  AuthManager.redirectTo(intendedUrl);
}

5. Token Configuration

{
  token: {
    headerName: 'Authorization',        // Header name for token
    cookieName: 'auth_token',          // Cookie name for access token
    refreshCookieName: 'refresh_token', // Cookie name for refresh token
    storageKey: 'auth_user',           // LocalStorage key for user data

    cookieOptions: {
      path: '/',                       // Cookie path
      secure: true,                    // HTTPS only (auto-detect)
      sameSite: 'Strict'              // CSRF protection
    },

    cookieMaxAge: null                 // Cookie max age (null = session)
  }
}

Cookie Options:

await AuthManager.init({
  token: {
    cookieOptions: {
      path: '/',              // Available on all paths
      secure: true,           // HTTPS only
      sameSite: 'Strict',     // Strict CSRF protection
      httpOnly: true          // Cannot be accessed by JavaScript
    },
    cookieMaxAge: 7 * 24 * 60 * 60  // 7 days
  }
});

Storage Options:

// Store user data in localStorage
await AuthManager.init({
  token: {
    storageKey: 'auth_user'
  }
});

// Access user data
const user = JSON.parse(localStorage.getItem('auth_user'));
console.log(user);

// Clear user data
localStorage.removeItem('auth_user');

Authentication Methods

1. Login

Basic Login

// Simple login
const result = await AuthManager.login({
  username: 'user@example.com',
  password: 'secret123'
});

if (result.success) {
  console.log('Login successful!');
  console.log('User:', result.user);
  // Will auto-redirect to afterLogin route
} else {
  console.error('Login failed:', result.error);
}

Login with Options

// Login with custom options
const result = await AuthManager.login(
  {
    username: 'user@example.com',
    password: 'secret123'
  },
  {
    remember: true,              // Remember user (longer token expiry)
    redirect: false,             // Don't auto-redirect
    saveIntendedUrl: true       // Save current URL for after login
  }
);

if (result.success) {
  // Manual redirect
  window.location.href = '/custom-dashboard';
}

Login Response Object

{
  success: boolean,           // Login success/failure
  user: {                     // User object (if success)
    id: number,
    name: string,
    email: string,
    roles: string[],
    permissions: string[]
  },
  token: string,              // Access token (if available)
  error: string,              // Error message (if failure)
  code: string               // Error code (if failure)
}

2. Logout

Basic Logout

// Simple logout
await AuthManager.logout();

console.log('Logged out successfully');
// Will auto-redirect to afterLogout route

Logout with Options

// Logout without calling server
await AuthManager.logout(false);

// Logout with custom redirect
await AuthManager.logout(true, {
  redirect: '/goodbye'
});

// Logout without redirect
await AuthManager.logout(true, {
  redirect: false
});

Logout Behavior

// On logout, AuthManager will:
// 1. Call logout endpoint (if callServer = true)
// 2. Clear tokens from cookies
// 3. Clear user data from localStorage
// 4. Reset authentication state
// 5. Emit 'logout' event
// 6. Redirect to afterLogout route (if redirect !== false)
// 7. Stop auto-refresh timer

3. Check Authentication Status

// Check if user is authenticated
if (AuthManager.isAuthenticated()) {
  console.log('User is logged in');
} else {
  console.log('User is not logged in');
}

// Get current user
const user = AuthManager.getUser();
if (user) {
  console.log('Welcome,', user.name);
  console.log('Roles:', user.roles);
  console.log('Permissions:', user.permissions);
}

// Check auth status with server verification
const isValid = await AuthManager.checkAuthStatus();
if (isValid) {
  console.log('Session is valid');
} else {
  console.log('Session expired or invalid');
}

4. Social Login

Google Login

// Google login
await AuthManager.socialLogin('google', {
  redirect_uri: 'http://localhost/auth/callback',
  scope: 'email profile'
});

// Will open popup or redirect to Google OAuth

Facebook Login

// Facebook login
await AuthManager.socialLogin('facebook', {
  redirect_uri: 'http://localhost/auth/callback',
  scope: 'email public_profile'
});

Handle OAuth Callback

// In your callback page (e.g., /auth/callback)
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const provider = params.get('provider');

if (code && provider) {
  const result = await AuthManager.handleAuthCallback({
    code,
    provider,
    state: params.get('state')
  });

  if (result.success) {
    console.log('Social login successful!');
    window.location.href = '/dashboard';
  } else {
    console.error('Social login failed:', result.error);
    window.location.href = '/login';
  }
}

Supported Providers

  • ✅ Google
  • ✅ Facebook
  • ✅ Custom OAuth providers

5. Login with Token

// Login with existing token (e.g., from email verification)
const result = await AuthManager.loginWithToken('eyJhbGciOiJIUzI1...', {
  fetchUser: true  // Fetch user data from server
});

if (result.success) {
  console.log('Logged in with token!');
  console.log('User:', result.user);
}

Token Management

1. Get Access Token

// Get current access token
const token = await AuthManager.fetchAccessToken();

if (token) {
  console.log('Access token:', token);
} else {
  console.log('No access token available');
}

// Ensure access token is valid
const validToken = await AuthManager.ensureAccessToken(token);
console.log('Valid token:', validToken);

2. Refresh Token

// Manual token refresh
const refreshed = await AuthManager.refreshToken();

if (refreshed) {
  console.log('Token refreshed successfully');

  // Get new token
  const newToken = await AuthManager.fetchAccessToken();
  console.log('New token:', newToken);
} else {
  console.error('Token refresh failed');

  // Logout user
  await AuthManager.logout(false);
  window.location.href = '/login';
}

3. Get Refresh Token

// Get refresh token (if available)
const refreshToken = AuthManager.getRefreshToken();

if (refreshToken) {
  console.log('Refresh token available');
} else {
  console.log('No refresh token');
}

4. Auto Token Refresh

// Auto token refresh is enabled by default
await AuthManager.init({
  security: {
    autoRefresh: true,
    refreshBeforeExpiry: 5 * 60 * 1000  // 5 minutes before expiry
  }
});

// AuthManager will:
// 1. Parse token to get expiry time
// 2. Set timer to refresh before expiry
// 3. Automatically call refreshToken() when timer triggers
// 4. Update token in cookies
// 5. Reset timer for new token

// Disable auto refresh
await AuthManager.init({
  security: {
    autoRefresh: false
  }
});

5. Token Service Integration

// AuthManager uses TokenService internally

// Get TokenService instance
const tokenService = AuthManager.tokenService;

// Parse token
const payload = tokenService.parseToken(token);
console.log('Token payload:', payload);

// Check if token is expired
const expired = tokenService.isTokenExpired(token);
console.log('Token expired:', expired);

// Get token expiry
const expiry = tokenService.getTokenExpiry(token);
console.log('Token expires at:', new Date(expiry * 1000));

// Get user ID from token
const userId = tokenService.getUserId(token);
console.log('User ID:', userId);

// Get user roles from token
const roles = tokenService.getUserRoles(token);
console.log('User roles:', roles);

Session Management

1. User State

// Get current user
const user = AuthManager.getUser();

if (user) {
  console.log('User ID:', user.id);
  console.log('Name:', user.name);
  console.log('Email:', user.email);
  console.log('Roles:', user.roles);
  console.log('Permissions:', user.permissions);
}

// Check if authenticated
const authenticated = AuthManager.isAuthenticated();
console.log('Authenticated:', authenticated);

// Get authentication state
console.log('Auth state:', AuthManager.state);
// {
//   initialized: true,
//   authenticated: true,
//   user: {...},
//   loading: false,
//   error: null,
//   loginAttempts: 0,
//   lockedUntil: null,
//   refreshTimer: 123
// }

2. Permission Checking

// Check if user has permission
if (AuthManager.hasPermission('edit:posts')) {
  console.log('User can edit posts');
  document.getElementById('editButton').style.display = 'block';
}

// Check if user has role
if (AuthManager.hasRole('admin')) {
  console.log('User is admin');
  document.getElementById('adminPanel').style.display = 'block';
}

// Check multiple permissions (AND logic)
if (AuthManager.hasPermission(['edit:posts', 'delete:posts'])) {
  console.log('User can edit and delete posts');
}

// Check multiple roles (OR logic)
if (AuthManager.hasRole(['admin', 'moderator'])) {
  console.log('User is admin or moderator');
}

3. Intended URL

// Save intended URL before redirect
AuthManager.saveIntendedUrl('/admin/settings');

// After login, redirect to intended URL
const intendedUrl = AuthManager.getIntendedUrl();
if (intendedUrl) {
  console.log('Redirecting to:', intendedUrl);
  AuthManager.redirectTo(intendedUrl);
} else {
  // Default redirect
  AuthManager.redirectTo('/dashboard');
}

// Clear intended URL
localStorage.removeItem('intended_url');

4. Clear Auth Data

// Clear all authentication data
AuthManager.clearAuthData();

// This will:
// 1. Clear tokens from cookies
// 2. Clear user data from localStorage
// 3. Reset state to initial
// 4. Stop auto-refresh timer
// 5. Emit 'auth:cleared' event

Security Features

1. CSRF Protection

// CSRF protection is enabled by default
await AuthManager.init({
  security: {
    csrf: true
  }
});

// Get CSRF token
const csrfToken = AuthManager.getCSRFToken();
console.log('CSRF Token:', csrfToken);

// Update CSRF token (from server response)
AuthManager.updateCSRFToken('new-csrf-token');

// CSRF token will be automatically:
// 1. Read from <meta name="csrf-token"> tag
// 2. Sent in X-CSRF-Token header with every request
// 3. Updated after successful requests

HTML Setup:

<head>
  <meta name="csrf-token" content="<?php echo $csrfToken; ?>">
</head>

Server Validation (PHP example):

// Get CSRF token from request
$requestToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';

// Get CSRF token from session
$sessionToken = $_SESSION['csrf_token'] ?? '';

// Validate
if ($requestToken !== $sessionToken) {
    http_response_code(403);
    echo json_encode(['error' => 'CSRF token mismatch']);
    exit;
}

2. Rate Limiting

// Rate limiting is enabled by default
await AuthManager.init({
  security: {
    rateLimiting: true,
    maxLoginAttempts: 5,
    lockoutTime: 30 * 60 * 1000  // 30 minutes
  }
});

// Check if user can login
const canLogin = AuthManager.checkRateLimit('user@example.com');

if (!canLogin) {
  console.log('Too many login attempts');
  alert('Account temporarily locked. Please try again later.');
  return;
}

// Record login attempt
AuthManager.recordLoginAttempt('user@example.com', success);

// Get rate limit data
console.log('Rate limit data:', AuthManager.rateLimitData);
// Map {
//   'user@example.com' => {
//     attempts: 3,
//     lockedUntil: null,
//     lastAttempt: 1234567890
//   }
// }

3. HttpOnly Cookies

// Tokens are stored in httpOnly cookies by default
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true,    // Cannot be accessed by JavaScript (XSS protection)
      secure: true,      // HTTPS only
      sameSite: 'Strict' // CSRF protection
    }
  }
});

// Benefits:
// ✅ Immune to XSS attacks (JavaScript cannot access cookies)
// ✅ Automatically sent with every request
// ✅ No need to manually include token in headers
// ✅ Server validates token from cookie

// Server should set cookies:
// Set-Cookie: auth_token=eyJhbG...; HttpOnly; Secure; SameSite=Strict; Path=/

4. Secure Token Handling

// Best practices for token handling

// ✅ DO: Use httpOnly cookies
await AuthManager.init({
  type: 'jwt-httponly'
});

// ✅ DO: Use HTTPS in production
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  location.replace(`https:${location.href.substring(location.protocol.length)}`);
}

// ✅ DO: Set short token expiry
// Server: JWT expiry = 15 minutes, Refresh token = 7 days

// ✅ DO: Rotate refresh tokens
// Server: Issue new refresh token on each refresh

// ❌ DON'T: Store tokens in localStorage
// localStorage.setItem('token', token); // Vulnerable to XSS

// ❌ DON'T: Send tokens in URL
// fetch('/api/users?token=' + token); // Leaked in logs

// ❌ DON'T: Use long-lived tokens
// JWT expiry = 30 days // Too long!

Events System

AuthManager emits events for lifecycle hooks.

Available Events

// Authentication events
document.addEventListener('auth:login', (e) => {
  console.log('User logged in:', e.detail.user);
});

document.addEventListener('auth:logout', (e) => {
  console.log('User logged out');
});

document.addEventListener('auth:error', (e) => {
  console.log('Auth error:', e.detail.error);
});

// Token events
document.addEventListener('auth:token:refreshed', (e) => {
  console.log('Token refreshed:', e.detail.token);
});

document.addEventListener('auth:token:expired', (e) => {
  console.log('Token expired');
});

// Session events
document.addEventListener('auth:session:verified', (e) => {
  console.log('Session verified:', e.detail.user);
});

document.addEventListener('auth:session:invalid', (e) => {
  console.log('Session invalid');
});

// State events
document.addEventListener('auth:state:changed', (e) => {
  console.log('Auth state changed:', e.detail.state);
});

Listen to Events

// Simple event listener
document.addEventListener('auth:login', (e) => {
  const { user, timestamp } = e.detail;
  console.log('Login at', new Date(timestamp));
  console.log('User:', user.name);
});

// Multiple events
['auth:login', 'auth:logout'].forEach(eventName => {
  document.addEventListener(eventName, (e) => {
    console.log('Auth event:', eventName, e.detail);
  });
});

// Remove event listener
const handler = (e) => console.log('Login event', e.detail);
document.addEventListener('auth:login', handler);

// Later...
document.removeEventListener('auth:login', handler);

Emit Custom Events

// AuthManager emits events internally
// But you can also emit custom events

AuthManager.emit('auth:custom', {
  message: 'Custom event',
  data: { foo: 'bar' }
});

// Listen to custom event
document.addEventListener('auth:custom', (e) => {
  console.log('Custom event:', e.detail.message);
  console.log('Data:', e.detail.data);
});

Usage Examples

1. Complete Login Form with Validation

<!DOCTYPE html>
<html>
<head>
  <title>Login</title>
  <style>
    .error { color: red; }
    .loading { opacity: 0.5; pointer-events: none; }
  </style>
</head>
<body>
  <form id="loginForm">
    <div>
      <label>Email:</label>
      <input type="email" id="username" required>
    </div>
    <div>
      <label>Password:</label>
      <input type="password" id="password" required>
    </div>
    <div>
      <label>
        <input type="checkbox" id="remember">
        Remember me
      </label>
    </div>
    <button type="submit" id="submitBtn">Login</button>
    <div id="error" class="error"></div>
  </form>

  <script src="/Now/js/TokenService.js"></script>
  <script src="/Now/js/AuthLoadingManager.js"></script>
  <script src="/Now/js/AuthErrorHandler.js"></script>
  <script src="/Now/js/AuthManager.js"></script>

  <script>
    (async () => {
      // Initialize AuthManager
      await AuthManager.init({
        enabled: true,
        endpoints: {
          login: '/api/v1/auth/login'
        },
        redirects: {
          afterLogin: '/dashboard'
        },
        security: {
          rateLimiting: true,
          maxLoginAttempts: 5
        }
      });

      // Check if already logged in
      if (AuthManager.isAuthenticated()) {
        window.location.href = '/dashboard';
        return;
      }

      const form = document.getElementById('loginForm');
      const submitBtn = document.getElementById('submitBtn');
      const errorDiv = document.getElementById('error');

      form.addEventListener('submit', async (e) => {
        e.preventDefault();

        // Clear previous error
        errorDiv.textContent = '';

        // Get form data
        const username = document.getElementById('username').value.trim();
        const password = document.getElementById('password').value;
        const remember = document.getElementById('remember').checked;

        // Validate
        if (!username || !password) {
          errorDiv.textContent = 'Please fill in all fields';
          return;
        }

        // Email validation
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(username)) {
          errorDiv.textContent = 'Please enter a valid email';
          return;
        }

        // Check rate limit
        if (!AuthManager.checkRateLimit(username)) {
          errorDiv.textContent = 'Too many login attempts. Please try again later.';
          return;
        }

        // Show loading
        form.classList.add('loading');
        submitBtn.textContent = 'Logging in...';
        submitBtn.disabled = true;

        try {
          // Login
          const result = await AuthManager.login(
            { username, password },
            { remember }
          );

          if (result.success) {
            // Success
            submitBtn.textContent = 'Success! Redirecting...';
            // Will auto-redirect
          } else {
            // Failed
            errorDiv.textContent = result.error || 'Login failed';
            submitBtn.textContent = 'Login';
            submitBtn.disabled = false;
          }
        } catch (error) {
          errorDiv.textContent = 'An error occurred. Please try again.';
          console.error('Login error:', error);
          submitBtn.textContent = 'Login';
          submitBtn.disabled = false;
        } finally {
          form.classList.remove('loading');
        }
      });

      // Listen to auth events
      document.addEventListener('auth:error', (e) => {
        errorDiv.textContent = e.detail.message || 'Authentication error';
      });
    })();
  </script>
</body>
</html>

2. Protected Dashboard with Logout

<!DOCTYPE html>
<html>
<head>
  <title>Dashboard</title>
</head>
<body>
  <div id="dashboard">
    <h1>Welcome, <span id="userName"></span>!</h1>
    <p>Email: <span id="userEmail"></span></p>
    <p>Roles: <span id="userRoles"></span></p>

    <div id="adminPanel" style="display: none;">
      <h2>Admin Panel</h2>
      <p>You have admin access</p>
    </div>

    <button id="logoutBtn">Logout</button>
  </div>

  <script src="/Now/js/TokenService.js"></script>
  <script src="/Now/js/AuthManager.js"></script>

  <script>
    (async () => {
      // Initialize AuthManager
      await AuthManager.init({
        enabled: true
      });

      // Check authentication
      if (!AuthManager.isAuthenticated()) {
        // Save intended URL
        AuthManager.saveIntendedUrl(window.location.pathname);
        // Redirect to login
        window.location.href = '/login';
        return;
      }

      // Verify session with server
      const valid = await AuthManager.checkAuthStatus();
      if (!valid) {
        alert('Your session has expired. Please login again.');
        await AuthManager.logout(false);
        window.location.href = '/login';
        return;
      }

      // Get user data
      const user = AuthManager.getUser();

      // Display user info
      document.getElementById('userName').textContent = user.name;
      document.getElementById('userEmail').textContent = user.email;
      document.getElementById('userRoles').textContent = user.roles.join(', ');

      // Show admin panel if user is admin
      if (AuthManager.hasRole('admin')) {
        document.getElementById('adminPanel').style.display = 'block';
      }

      // Handle logout
      document.getElementById('logoutBtn').addEventListener('click', async () => {
        if (confirm('Are you sure you want to logout?')) {
          await AuthManager.logout();
          // Will auto-redirect to /login
        }
      });

      // Handle session expiry
      document.addEventListener('auth:token:expired', () => {
        alert('Your session has expired. Please login again.');
        window.location.href = '/login';
      });

      // Handle unauthorized errors
      document.addEventListener('auth:error', (e) => {
        if (e.detail.errorType === 'UNAUTHORIZED') {
          alert('You are not authorized. Please login again.');
          window.location.href = '/login';
        }
      });
    })();
  </script>
</body>
</html>

3. Social Login Integration

<!DOCTYPE html>
<html>
<head>
  <title>Login with Social</title>
  <style>
    .social-btn {
      display: block;
      width: 200px;
      padding: 10px;
      margin: 10px 0;
      cursor: pointer;
    }
    .google { background: #4285F4; color: white; }
    .facebook { background: #1877F2; color: white; }
    .github { background: #333; color: white; }
  </style>
</head>
<body>
  <h1>Login</h1>

  <div>
    <button class="social-btn google" id="googleLogin">
      Login with Google
    </button>
    <button class="social-btn facebook" id="facebookLogin">
      Login with Facebook
    </button>
    <button class="social-btn github" id="githubLogin">
      Login with GitHub
    </button>
  </div>

  <script src="/Now/js/TokenService.js"></script>
  <script src="/Now/js/AuthManager.js"></script>

  <script>
    (async () => {
      // Initialize AuthManager
      await AuthManager.init({
        enabled: true,
        endpoints: {
          social: '/api/v1/auth/social/{provider}',
          callback: '/api/v1/auth/callback'
        },
        redirects: {
          afterLogin: '/dashboard'
        }
      });

      // Google login
      document.getElementById('googleLogin').addEventListener('click', async () => {
        await AuthManager.socialLogin('google', {
          redirect_uri: window.location.origin + '/auth/callback',
          scope: 'email profile'
        });
      });

      // Facebook login
      document.getElementById('facebookLogin').addEventListener('click', async () => {
        await AuthManager.socialLogin('facebook', {
          redirect_uri: window.location.origin + '/auth/callback',
          scope: 'email public_profile'
        });
      });

      // GitHub login
      document.getElementById('githubLogin').addEventListener('click', async () => {
        await AuthManager.socialLogin('github', {
          redirect_uri: window.location.origin + '/auth/callback',
          scope: 'user:email'
        });
      });

      // Handle OAuth callback (if on callback page)
      if (window.location.pathname === '/auth/callback') {
        const params = new URLSearchParams(window.location.search);
        const code = params.get('code');
        const provider = params.get('provider');

        if (code && provider) {
          console.log('Processing OAuth callback...');

          const result = await AuthManager.handleAuthCallback({
            code,
            provider,
            state: params.get('state')
          });

          if (result.success) {
            console.log('Social login successful!');
            window.location.href = '/dashboard';
          } else {
            console.error('Social login failed:', result.error);
            alert('Social login failed: ' + result.error);
            window.location.href = '/login';
          }
        }
      }
    })();
  </script>
</body>
</html>

4. Role-Based Access Control

// Complete RBAC example
(async () => {
  await AuthManager.init({ enabled: true });

  if (!AuthManager.isAuthenticated()) {
    window.location.href = '/login';
    return;
  }

  const user = AuthManager.getUser();

  // Define UI elements visibility based on roles
  const rolePermissions = {
    admin: ['view:dashboard', 'edit:users', 'delete:users', 'view:reports'],
    moderator: ['view:dashboard', 'edit:posts', 'delete:posts'],
    user: ['view:dashboard', 'edit:own-posts']
  };

  // Check permissions
  function hasAnyPermission(permissions) {
    return permissions.some(perm => AuthManager.hasPermission(perm));
  }

  // Show/hide UI elements
  if (AuthManager.hasRole('admin')) {
    document.getElementById('adminPanel').style.display = 'block';
    document.getElementById('userManagement').style.display = 'block';
    document.getElementById('reports').style.display = 'block';
  } else if (AuthManager.hasRole('moderator')) {
    document.getElementById('moderatorPanel').style.display = 'block';
    document.getElementById('contentManagement').style.display = 'block';
  }

  // Check specific permissions
  if (AuthManager.hasPermission('edit:users')) {
    document.querySelectorAll('.edit-user-btn').forEach(btn => {
      btn.style.display = 'inline-block';
    });
  }

  if (AuthManager.hasPermission('delete:users')) {
    document.querySelectorAll('.delete-user-btn').forEach(btn => {
      btn.style.display = 'inline-block';
    });
  }
})();

5. API Integration with Auto-Auth

// AuthManager automatically integrates with ApiService/HttpClient
(async () => {
  await AuthManager.init({ enabled: true });

  // Make authenticated request
  // Token is automatically included in headers
  const response = await fetch('/api/users', {
    method: 'GET',
    credentials: 'include'  // Include cookies
  });

  if (response.status === 401) {
    // Token expired - AuthManager will handle automatically
    console.log('Token expired - refreshing...');

    // AuthManager interceptor will:
    // 1. Try to refresh token
    // 2. Retry the request with new token
    // 3. Or logout if refresh fails
  } else if (response.ok) {
    const data = await response.json();
    console.log('Users:', data);
  }
})();

// Using ApiService (recommended)
await ApiService.init({
  baseURL: '/api',
  auth: {
    enabled: true,
    manager: AuthManager
  }
});

// All requests will include auth token automatically
const users = await ApiService.get('/users');
console.log(users.data);

// Create post (authenticated)
const post = await ApiService.post('/posts', {
  title: 'My Post',
  content: 'Post content'
});
console.log(post.data);

6. Session Timeout Handler

// Handle session timeout
(async () => {
  await AuthManager.init({ enabled: true });

  const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
  const WARNING_TIME = 5 * 60 * 1000;     // 5 minutes before timeout

  let sessionTimer;
  let warningTimer;

  function resetSessionTimer() {
    // Clear existing timers
    clearTimeout(sessionTimer);
    clearTimeout(warningTimer);

    // Set warning timer
    warningTimer = setTimeout(() => {
      const remaining = Math.floor(WARNING_TIME / 1000 / 60);
      const continueSession = confirm(
        `Your session will expire in ${remaining} minutes. Do you want to continue?`
      );

      if (continueSession) {
        // Refresh token to extend session
        AuthManager.refreshToken();
        resetSessionTimer();
      }
    }, SESSION_TIMEOUT - WARNING_TIME);

    // Set session timeout
    sessionTimer = setTimeout(() => {
      alert('Your session has expired. Please login again.');
      AuthManager.logout();
    }, SESSION_TIMEOUT);
  }

  // Reset timer on user activity
  ['click', 'keypress', 'scroll', 'mousemove'].forEach(event => {
    document.addEventListener(event, () => {
      if (AuthManager.isAuthenticated()) {
        resetSessionTimer();
      }
    }, { passive: true });
  });

  // Start session timer
  if (AuthManager.isAuthenticated()) {
    resetSessionTimer();
  }

  // Clear timer on logout
  document.addEventListener('auth:logout', () => {
    clearTimeout(sessionTimer);
    clearTimeout(warningTimer);
  });
})();

API Reference

Methods

init(options)

Initialize AuthManager with configuration.

Parameters:

  • options (Object, optional) - Configuration object

Returns: Promise<void>

Example:

await AuthManager.init({
  enabled: true,
  type: 'jwt-httponly',
  endpoints: {
    login: '/api/auth/login',
    logout: '/api/auth/logout'
  }
});

isInitialized()

Check if AuthManager is initialized.

Returns: boolean - True if initialized

Example:

if (AuthManager.isInitialized()) {
  console.log('AuthManager is ready');
}

login(credentials, options)

Login user with credentials.

Parameters:

  • credentials (Object) - Login credentials
    • username (string) - Username or email
    • password (string) - Password
  • options (Object, optional) - Login options
    • remember (boolean) - Remember user (default: false)
    • redirect (boolean|string) - Redirect after login (default: true)
    • saveIntendedUrl (boolean) - Save intended URL (default: false)

Returns: Promise<Object> - Login result

{
  success: boolean,
  user: Object,
  token: string,
  error: string,
  code: string
}

Example:

const result = await AuthManager.login({
  username: 'user@example.com',
  password: 'secret123'
}, {
  remember: true,
  redirect: '/dashboard'
});

if (result.success) {
  console.log('Login successful');
}

logout(callServer, options)

Logout current user.

Parameters:

  • callServer (boolean, optional) - Call logout endpoint (default: true)
  • options (Object, optional) - Logout options
    • redirect (boolean|string) - Redirect after logout (default: true)

Returns: Promise<void>

Example:

// Logout with server call
await AuthManager.logout();

// Logout without server call
await AuthManager.logout(false);

// Logout with custom redirect
await AuthManager.logout(true, {
  redirect: '/goodbye'
});

isAuthenticated()

Check if user is authenticated.

Returns: boolean - True if authenticated

Example:

if (AuthManager.isAuthenticated()) {
  console.log('User is logged in');
}

getUser()

Get current user object.

Returns: Object|null - User object or null

Example:

const user = AuthManager.getUser();
if (user) {
  console.log('User:', user.name);
  console.log('Roles:', user.roles);
}

checkAuthStatus()

Verify authentication status with server.

Returns: Promise<boolean> - True if valid

Example:

const valid = await AuthManager.checkAuthStatus();
if (!valid) {
  console.log('Session expired');
  await AuthManager.logout(false);
}

refreshToken()

Refresh access token.

Returns: Promise<boolean> - True if successful

Example:

const refreshed = await AuthManager.refreshToken();
if (refreshed) {
  console.log('Token refreshed');
} else {
  console.log('Refresh failed');
  await AuthManager.logout(false);
}

hasRole(role)

Check if user has role(s).

Parameters:

  • role (string|Array) - Role name(s) to check

Returns: boolean - True if user has role

Example:

// Single role
if (AuthManager.hasRole('admin')) {
  console.log('User is admin');
}

// Multiple roles (OR logic)
if (AuthManager.hasRole(['admin', 'moderator'])) {
  console.log('User is admin or moderator');
}

hasPermission(permission)

Check if user has permission(s).

Parameters:

  • permission (string|Array) - Permission name(s) to check

Returns: boolean - True if user has permission

Example:

// Single permission
if (AuthManager.hasPermission('edit:posts')) {
  console.log('User can edit posts');
}

// Multiple permissions (AND logic)
if (AuthManager.hasPermission(['edit:posts', 'delete:posts'])) {
  console.log('User can edit and delete posts');
}

socialLogin(provider, options)

Login with social provider.

Parameters:

  • provider (string) - Provider name (google, facebook, github, etc.)
  • options (Object, optional) - Social login options
    • redirect_uri (string) - Callback URL
    • scope (string) - OAuth scope
    • state (string) - OAuth state

Returns: Promise<void>

Example:

await AuthManager.socialLogin('google', {
  redirect_uri: 'http://localhost/auth/callback',
  scope: 'email profile'
});

handleAuthCallback(callbackData)

Handle OAuth callback.

Parameters:

  • callbackData (Object) - Callback data
    • code (string) - Authorization code
    • provider (string) - Provider name
    • state (string, optional) - OAuth state

Returns: Promise<Object> - Callback result

Example:

const result = await AuthManager.handleAuthCallback({
  code: params.get('code'),
  provider: params.get('provider')
});

if (result.success) {
  window.location.href = '/dashboard';
}

loginWithToken(token, options)

Login with existing token.

Parameters:

  • token (string) - Access token
  • options (Object, optional) - Login options
    • fetchUser (boolean) - Fetch user data (default: true)
    • redirect (boolean|string) - Redirect after login (default: true)

Returns: Promise<Object> - Login result

Example:

const result = await AuthManager.loginWithToken('eyJhbGciOiJIUzI1...', {
  fetchUser: true,
  redirect: '/dashboard'
});

getCSRFToken()

Get current CSRF token.

Returns: string|null - CSRF token or null

Example:

const csrfToken = AuthManager.getCSRFToken();
console.log('CSRF Token:', csrfToken);

updateCSRFToken(token)

Update CSRF token.

Parameters:

  • token (string) - New CSRF token

Returns: void

Example:

AuthManager.updateCSRFToken('new-csrf-token');

saveIntendedUrl(url)

Save intended URL for redirect after login.

Parameters:

  • url (string, optional) - URL to save (default: current URL)

Returns: void

Example:

// Save current URL
AuthManager.saveIntendedUrl();

// Save specific URL
AuthManager.saveIntendedUrl('/admin/settings');

getIntendedUrl()

Get saved intended URL.

Returns: string|null - Intended URL or null

Example:

const intendedUrl = AuthManager.getIntendedUrl();
if (intendedUrl) {
  AuthManager.redirectTo(intendedUrl);
}

redirectTo(url)

Redirect to URL using router or window.location.

Parameters:

  • url (string) - URL to redirect to

Returns: void

Example:

AuthManager.redirectTo('/dashboard');

clearAuthData()

Clear all authentication data.

Returns: void

Example:

AuthManager.clearAuthData();

emit(eventName, data)

Emit custom event.

Parameters:

  • eventName (string) - Event name (should start with 'auth:')
  • data (Object, optional) - Event data

Returns: void

Example:

AuthManager.emit('auth:custom', {
  message: 'Custom event',
  data: { foo: 'bar' }
});

checkRateLimit(identifier)

Check if identifier is rate-limited.

Parameters:

  • identifier (string) - Identifier to check (e.g., username)

Returns: boolean - True if allowed

Example:

const canLogin = AuthManager.checkRateLimit('user@example.com');
if (!canLogin) {
  alert('Too many login attempts');
}

recordLoginAttempt(identifier, success)

Record login attempt.

Parameters:

  • identifier (string) - Identifier (e.g., username)
  • success (boolean) - Whether login succeeded

Returns: void

Example:

AuthManager.recordLoginAttempt('user@example.com', false);

Properties

config

Current configuration object.

Type: Object

Example:

console.log(AuthManager.config);
// {
//   enabled: true,
//   type: 'jwt-httponly',
//   endpoints: {...},
//   security: {...},
//   redirects: {...},
//   token: {...}
// }

state

Current authentication state.

Type: Object

Example:

console.log(AuthManager.state);
// {
//   initialized: true,
//   authenticated: true,
//   user: {...},
//   loading: false,
//   error: null,
//   loginAttempts: 0,
//   lockedUntil: null,
//   refreshTimer: 123
// }

tokenService

TokenService instance.

Type: TokenService

Example:

const tokenService = AuthManager.tokenService;
const payload = tokenService.parseToken(token);

Events

Event Name Description Detail Properties
auth:login User logged in user, timestamp
auth:logout User logged out timestamp
auth:error Authentication error error, errorType, message
auth:token:refreshed Token refreshed token, timestamp
auth:token:expired Token expired timestamp
auth:session:verified Session verified user, timestamp
auth:session:invalid Session invalid timestamp
auth:state:changed State changed state, timestamp

Best Practices

1. Always Initialize Before Use

// ✅ Good - initialize first
(async () => {
  await AuthManager.init({ enabled: true });

  if (AuthManager.isAuthenticated()) {
    console.log('User is logged in');
  }
})();

// ❌ Bad - use without initialization
if (AuthManager.isAuthenticated()) {  // May not work correctly
  console.log('User is logged in');
}

2. Handle Errors Properly

// ✅ Good - comprehensive error handling
try {
  const result = await AuthManager.login(credentials);

  if (result.success) {
    console.log('Login successful');
  } else {
    // Handle login failure
    switch (result.code) {
      case 'INVALID_CREDENTIALS':
        alert('Invalid username or password');
        break;
      case 'ACCOUNT_LOCKED':
        alert('Your account has been locked');
        break;
      default:
        alert(result.error || 'Login failed');
    }
  }
} catch (error) {
  console.error('Login error:', error);
  alert('An error occurred. Please try again.');
}

// ❌ Bad - no error handling
const result = await AuthManager.login(credentials);
if (result.success) {
  console.log('Success');
}

3. Verify Session on Critical Operations

// ✅ Good - verify before critical operation
async function deleteUser(userId) {
  // Verify session first
  const valid = await AuthManager.checkAuthStatus();

  if (!valid) {
    alert('Your session has expired. Please login again.');
    await AuthManager.logout(false);
    return;
  }

  // Proceed with deletion
  const response = await ApiService.delete(`/users/${userId}`);

  if (response.ok) {
    console.log('User deleted');
  }
}

// ❌ Bad - no verification
async function deleteUser(userId) {
  const response = await ApiService.delete(`/users/${userId}`);
  // May fail if session expired
}

4. Use Event Listeners for State Changes

// ✅ Good - listen to auth events
document.addEventListener('auth:login', (e) => {
  console.log('User logged in:', e.detail.user);
  // Update UI
  updateUserInterface(e.detail.user);
});

document.addEventListener('auth:logout', () => {
  console.log('User logged out');
  // Clear UI
  clearUserInterface();
});

document.addEventListener('auth:token:expired', () => {
  alert('Your session has expired');
  window.location.href = '/login';
});

// ❌ Bad - poll for state changes
setInterval(() => {
  if (AuthManager.isAuthenticated()) {
    // Update UI
  }
}, 1000);  // Inefficient!

5. Implement Rate Limiting on Client

// ✅ Good - check rate limit before login
async function handleLogin(credentials) {
  // Check rate limit
  if (!AuthManager.checkRateLimit(credentials.username)) {
    alert('Too many login attempts. Please try again later.');
    return;
  }

  const result = await AuthManager.login(credentials);

  // Record attempt
  AuthManager.recordLoginAttempt(credentials.username, result.success);

  if (result.success) {
    console.log('Login successful');
  }
}

// ❌ Bad - no rate limiting
async function handleLogin(credentials) {
  const result = await AuthManager.login(credentials);
  // User can spam login attempts
}

6. Clear Sensitive Data on Logout

// ✅ Good - clear everything on logout
async function handleLogout() {
  // Clear form data
  document.querySelectorAll('input').forEach(input => {
    input.value = '';
  });

  // Clear cached data
  sessionStorage.clear();

  // Logout
  await AuthManager.logout();

  // Clear browser history
  if (history.replaceState) {
    history.replaceState(null, '', '/login');
  }
}

// ❌ Bad - leave sensitive data
async function handleLogout() {
  await AuthManager.logout();
  // Form data, cache, history still contain sensitive info
}

7. Use HTTPS in Production

// ✅ Good - enforce HTTPS
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
  location.replace(`https:${location.href.substring(location.protocol.length)}`);
}

await AuthManager.init({
  token: {
    cookieOptions: {
      secure: true  // Only send over HTTPS
    }
  }
});

// ❌ Bad - allow HTTP in production
await AuthManager.init({
  token: {
    cookieOptions: {
      secure: false  // Insecure!
    }
  }
});

8. Implement Session Timeout

// ✅ Good - session timeout with warning
const SESSION_TIMEOUT = 30 * 60 * 1000;  // 30 minutes
let sessionTimer;

function resetSessionTimer() {
  clearTimeout(sessionTimer);

  sessionTimer = setTimeout(() => {
    alert('Your session has expired');
    AuthManager.logout();
  }, SESSION_TIMEOUT);
}

// Reset on activity
document.addEventListener('click', resetSessionTimer);
document.addEventListener('keypress', resetSessionTimer);

// Start timer
if (AuthManager.isAuthenticated()) {
  resetSessionTimer();
}

// ❌ Bad - no session timeout
// Sessions never expire on client side

Common Pitfalls

1. Not Checking Initialization

// ❌ Bad - use without checking initialization
if (AuthManager.isAuthenticated()) {
  console.log('Logged in');
}

// ✅ Good - check initialization first
if (AuthManager.isInitialized() && AuthManager.isAuthenticated()) {
  console.log('Logged in');
}

// ✅ Better - await initialization
(async () => {
  await AuthManager.init();

  if (AuthManager.isAuthenticated()) {
    console.log('Logged in');
  }
})();

2. Storing Tokens in localStorage

// ❌ Bad - vulnerable to XSS
localStorage.setItem('token', token);
localStorage.setItem('refresh_token', refreshToken);

// ✅ Good - use httpOnly cookies
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true,
      secure: true,
      sameSite: 'Strict'
    }
  }
});

3. Not Handling Token Expiry

// ❌ Bad - no expiry handling
const response = await fetch('/api/users', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

if (response.status === 401) {
  alert('Unauthorized');
}

// ✅ Good - auto-refresh on expiry
// AuthManager handles this automatically via interceptors
const response = await ApiService.get('/users');

if (!response.ok) {
  console.error('Error:', response.error);
}

4. Infinite Redirect Loops

// ❌ Bad - can cause infinite loop
if (!AuthManager.isAuthenticated()) {
  window.location.href = '/login';
}

// On login page:
if (AuthManager.isAuthenticated()) {
  window.location.href = '/dashboard';
}

// ✅ Good - check current path
if (!AuthManager.isAuthenticated() && window.location.pathname !== '/login') {
  window.location.href = '/login';
}

if (AuthManager.isAuthenticated() && window.location.pathname === '/login') {
  window.location.href = '/dashboard';
}

5. Not Validating User Input

// ❌ Bad - no validation
async function handleLogin(e) {
  e.preventDefault();

  const result = await AuthManager.login({
    username: document.getElementById('username').value,
    password: document.getElementById('password').value
  });
}

// ✅ Good - validate input
async function handleLogin(e) {
  e.preventDefault();

  const username = document.getElementById('username').value.trim();
  const password = document.getElementById('password').value;

  // Validation
  if (!username || !password) {
    alert('Please fill in all fields');
    return;
  }

  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
    alert('Please enter a valid email');
    return;
  }

  if (password.length < 8) {
    alert('Password must be at least 8 characters');
    return;
  }

  const result = await AuthManager.login({ username, password });
}

6. Not Handling Network Errors

// ❌ Bad - no network error handling
const result = await AuthManager.login(credentials);

if (result.success) {
  console.log('Success');
} else {
  alert(result.error);
}

// ✅ Good - handle network errors
try {
  const result = await AuthManager.login(credentials);

  if (result.success) {
    console.log('Success');
  } else {
    alert(result.error || 'Login failed');
  }
} catch (error) {
  if (error.message.includes('network') || error.message.includes('fetch')) {
    alert('Network error. Please check your connection.');
  } else {
    alert('An error occurred. Please try again.');
  }
  console.error('Login error:', error);
}

Performance Considerations

1. Minimize Auth State Checks

// ❌ Bad - check on every request
async function makeRequest(url) {
  if (!AuthManager.isAuthenticated()) {
    window.location.href = '/login';
    return;
  }

  const valid = await AuthManager.checkAuthStatus();
  if (!valid) {
    await AuthManager.logout();
    return;
  }

  return fetch(url);
}

// ✅ Good - check once on page load, rely on interceptors
(async () => {
  await AuthManager.init();

  if (!AuthManager.isAuthenticated()) {
    window.location.href = '/login';
    return;
  }

  // No need to check again on every request
  // Interceptors handle 401 errors automatically
})();

2. Cache User Data

// ❌ Bad - fetch user on every access
async function getUserName() {
  const response = await fetch('/api/auth/me');
  const user = await response.json();
  return user.name;
}

document.getElementById('userName').textContent = await getUserName();
document.getElementById('userEmail').textContent = await getUserName();  // Redundant!

// ✅ Good - use cached user data
const user = AuthManager.getUser();
document.getElementById('userName').textContent = user.name;
document.getElementById('userEmail').textContent = user.email;

3. Debounce Token Refresh

// ✅ Good - AuthManager already debounces token refresh
// Multiple simultaneous requests won't trigger multiple refreshes

// ❌ Bad - manual refresh without debounce
async function makeAuthenticatedRequest(url) {
  const token = await AuthManager.fetchAccessToken();

  if (TokenService.isTokenExpired(token)) {
    await AuthManager.refreshToken();  // Called multiple times!
  }

  return fetch(url, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
}

4. Optimize Event Listeners

// ❌ Bad - add multiple listeners
function initAuthListeners() {
  document.addEventListener('auth:login', handleLogin);
  document.addEventListener('auth:login', updateUI);
  document.addEventListener('auth:login', logEvent);
  // Called multiple times = multiple listeners!
}

// ✅ Good - single listener, delegate tasks
let authListenersInitialized = false;

function initAuthListeners() {
  if (authListenersInitialized) return;

  document.addEventListener('auth:login', (e) => {
    handleLogin(e);
    updateUI(e);
    logEvent(e);
  });

  authListenersInitialized = true;
}

Security Considerations

1. XSS Protection

// ✅ Use httpOnly cookies
await AuthManager.init({
  type: 'jwt-httponly',
  token: {
    cookieOptions: {
      httpOnly: true  // JavaScript cannot access
    }
  }
});

// ✅ Sanitize user input
function sanitize(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

const user = AuthManager.getUser();
document.getElementById('userName').textContent = sanitize(user.name);

// ❌ DON'T use innerHTML with user data
// document.getElementById('userName').innerHTML = user.name;  // XSS risk!

2. CSRF Protection

// ✅ Enable CSRF protection
await AuthManager.init({
  security: {
    csrf: true,
    csrfIncludeSafeMethods: true
  }
});

// ✅ Server should validate CSRF token
// PHP example:
// if ($_SERVER['HTTP_X_CSRF_TOKEN'] !== $_SESSION['csrf_token']) {
//   http_response_code(403);
//   die('CSRF token mismatch');
// }

// ❌ DON'T disable CSRF protection
// await AuthManager.init({ security: { csrf: false } });  // Insecure!

3. Token Security

// ✅ Use short-lived access tokens
// Server: JWT expiry = 15 minutes

// ✅ Use long-lived refresh tokens
// Server: Refresh token expiry = 7 days

// ✅ Rotate refresh tokens
// Server: Issue new refresh token on each refresh

// ✅ Validate tokens on server
// Server: Verify signature, expiry, issuer, audience

// ❌ DON'T use long-lived access tokens
// Server: JWT expiry = 30 days  // Too long!

// ❌ DON'T reuse refresh tokens
// Server: Keep same refresh token  // Security risk!

4. Password Security

// ✅ Enforce strong passwords (client-side)
function validatePassword(password) {
  if (password.length < 8) {
    return 'Password must be at least 8 characters';
  }

  if (!/[A-Z]/.test(password)) {
    return 'Password must contain uppercase letter';
  }

  if (!/[a-z]/.test(password)) {
    return 'Password must contain lowercase letter';
  }

  if (!/\d/.test(password)) {
    return 'Password must contain number';
  }

  if (!/[!@#$%^&*]/.test(password)) {
    return 'Password must contain special character';
  }

  return null;
}

// ✅ Hash passwords on server
// Server (Node.js): const hashedPassword = await bcrypt.hash(password, 10);

// ❌ DON'T send passwords in GET requests
// fetch('/login?password=' + password);  // Leaked in logs!

// ❌ DON'T store passwords in plaintext (server-side)
// db.query('INSERT INTO users (password) VALUES (?)', [password]);  // Insecure!

5. Rate Limiting

// ✅ Implement rate limiting
await AuthManager.init({
  security: {
    rateLimiting: true,
    maxLoginAttempts: 5,
    lockoutTime: 30 * 60 * 1000
  }
});

// ✅ Server-side rate limiting (Express example)
// const rateLimit = require('express-rate-limit');
// const loginLimiter = rateLimit({
//   windowMs: 15 * 60 * 1000,
//   max: 5
// });
// app.post('/api/auth/login', loginLimiter, loginHandler);

// ❌ DON'T rely only on client-side rate limiting
// Server must also implement rate limiting

6. Session Security

// ✅ Implement session timeout
const SESSION_TIMEOUT = 30 * 60 * 1000;
let sessionTimer;

function resetSessionTimer() {
  clearTimeout(sessionTimer);
  sessionTimer = setTimeout(() => {
    AuthManager.logout();
  }, SESSION_TIMEOUT);
}

document.addEventListener('click', resetSessionTimer);

// ✅ Clear session on logout
await AuthManager.logout();  // Clears tokens, user data

// ✅ Verify session on critical operations
const valid = await AuthManager.checkAuthStatus();
if (!valid) {
  await AuthManager.logout(false);
}

// ❌ DON'T allow indefinite sessions
// No timeout = security risk!

Browser Compatibility

AuthManager รองรับ browsers สมัยใหม่:

  • ✅ Chrome 90+
  • ✅ Firefox 88+
  • ✅ Safari 14+
  • ✅ Edge 90+
  • ❌ IE 11 (ไม่รองรับ)

Required Features:

  • Promises & async/await
  • Fetch API
  • ES6+ features (arrow functions, destructuring, etc.)
  • httpOnly Cookies
  • Web Storage API (localStorage)
  • Custom Events