Now.js Framework Documentation
AuthManager - Core Authentication Manager
AuthManager - Core Authentication Manager
Documentation for AuthManager, the core Authentication Manager of the Now.js Framework
📋 Table of Contents
- Overview
- Installation & Import
- Getting Started
- Configuration
- Authentication Methods
- Token Management
- Session Management
- Security Features
- Events System
- Usage Examples
- API Reference
- Best Practices
- Common Pitfalls
- Performance Considerations
- 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 accessNow.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 objectGetting 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 serverRate 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 routeLogout 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 timer3. 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 OAuthFacebook 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
- ✅ 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' eventSecurity 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 requestsHTML 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 credentialsusername(string) - Username or emailpassword(string) - Password
options(Object, optional) - Login optionsremember(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 optionsredirect(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 optionsredirect_uri(string) - Callback URLscope(string) - OAuth scopestate(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 datacode(string) - Authorization codeprovider(string) - Provider namestate(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 tokenoptions(Object, optional) - Login optionsfetchUser(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 sideCommon 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 limiting6. 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
Related Documentation
- Authentication.md - Authentication system overview
- TokenService.md - JWT token management
- AuthGuard.md - Route protection
- AuthErrorHandler.md - Error handling
- AuthLoadingManager.md - Loading states
- Router.md - Routing system
- ApiService.md - HTTP service