# JWT Refresh Token Implementation Guide ## Overview The JWT authentication system supports both **cookie-based** and **header-based** (Bearer token) authentication with comprehensive refresh token functionality and proper logout logic. **All authentication methods now use refresh tokens** - there is no legacy single-token mode. ## Features - **Dual Authentication Methods**: Support for both cookie-based and Bearer token authentication - **Universal Refresh Tokens**: All logins receive both access and refresh tokens - **Automatic Token Refresh**: Tokens are refreshed when 75% of their lifetime has passed - **Logout Functionality**: Proper token blacklisting and cleanup - **Security**: Short-lived access tokens (30 minutes) and longer-lived refresh tokens (7 days) ## Authentication Methods ### 1. Cookie-Based Authentication - Access token stored in `auth_token` cookie - Refresh token stored in `refresh_token` cookie - Suitable for web applications with same-origin requests - Tokens also returned in response body ### 2. Bearer Token Authentication - Access token sent in `Authorization: Bearer ` header - Refresh token sent in `X-Refresh-Token` header - Suitable for mobile apps, SPAs, and API integrations - Tokens returned in response body ## API Endpoints ### Login ```http POST /api/user/login Content-Type: application/json { "username": "user@example.com", "password": "password123" } ``` **Response (all logins):** ```json { "user": { ... }, "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` For cookie-based auth, tokens are also set as httpOnly cookies. ### Refresh Token ```http POST /api/user/refresh-token ``` **For Cookie-based auth:** - Refresh token is read from `refresh_token` cookie - New tokens are set as cookies AND returned in response body **For Bearer token auth:** ```http POST /api/user/refresh-token X-Refresh-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **Response:** ```json { "success": true, "message": "Tokens refreshed successfully", "accessToken": "new_access_token", "refreshToken": "new_refresh_token" } ``` ### Logout ```http POST /api/user/logout Authorization: Bearer ``` Response: ```json { "success": true } ``` ## Environment Variables ```env # JWT Configuration JWT_SECRET=your-secret-key-for-access-tokens JWT_REFRESH_SECRET=your-secret-key-for-refresh-tokens # Access Token Expiry (use one of these) JWT_ACCESS_TOKEN_EXPIRY=1800 # Access token expiry in seconds (30 minutes) JWT_ACCESS_TOKEN_EXPIRATION=30m # Access token expiry (supports s, m, h, d) JWT_EXPIRY=1800 # Legacy: Access token expiry in seconds JWT_EXPIRATION=30m # Legacy: Access token expiry with duration # Refresh Token Expiry (use one of these) JWT_REFRESH_TOKEN_EXPIRY=604800 # Refresh token expiry in seconds (7 days) JWT_REFRESH_TOKEN_EXPIRATION=7d # Refresh token expiry (supports s, m, h, d) JWT_REFRESH_EXPIRATION=7d # Legacy: Refresh token expiry with duration # Cookie Names (optional) JWT_COOKIE_NAME=auth_token # Access token cookie name (default: auth_token) JWT_REFRESH_COOKIE_NAME=refresh_token # Refresh token cookie name (default: refresh_token) ``` ### Environment Variable Priority **Access Token Expiry** (checked in order): 1. `JWT_ACCESS_TOKEN_EXPIRY` (seconds) 2. `JWT_ACCESS_TOKEN_EXPIRATION` (duration string) 3. `JWT_EXPIRY` (seconds) - legacy 4. `JWT_EXPIRATION` (duration string) - legacy 5. Default: 1800 seconds (30 minutes) **Refresh Token Expiry** (checked in order): 1. `JWT_REFRESH_TOKEN_EXPIRY` (seconds) 2. `JWT_REFRESH_TOKEN_EXPIRATION` (duration string) 3. `JWT_REFRESH_EXPIRATION` (duration string) - legacy 4. Default: 604800 seconds (7 days) ### Duration String Format Supports: `s` (seconds), `m` (minutes), `h` (hours), `d` (days) Examples: `30s`, `15m`, `2h`, `7d` ## Token Structure ### Access Token Payload ```json { "userId": "user-uuid", "authLevel": 0, "userStatus": 1, "orgId": "org-uuid", "type": "access", "iat": 1640995200, "exp": 1640997000 } ``` ### Refresh Token Payload ```json { "userId": "user-uuid", "orgId": "org-uuid", "type": "refresh", "iat": 1640995200, "exp": 1641600000 } ``` ## Automatic Token Refresh The system automatically refreshes tokens when: - Token is within 25% of its expiration time (75% of lifetime has passed) - Valid refresh token is available - User makes an authenticated request **✅ Automatic refresh happens on every authenticated API call** - no manual intervention needed! ### Response Headers For Bearer token authentication, refresh responses include: - `X-New-Access-Token`: New access token - `X-New-Refresh-Token`: New refresh token - `X-Token-Refreshed`: "true" indicator ### Manual Refresh (Optional) While automatic refresh handles most scenarios, manual refresh is available for: - **Proactive refresh**: Before critical operations - **Background apps**: Long-running applications that need fresh tokens - **Offline recovery**: When app reconnects after being offline ```http POST /api/user/refresh-token X-Refresh-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ## Client Implementation Examples ### JavaScript/TypeScript (Fetch API) ```typescript class ApiClient { private accessToken: string = ''; private refreshToken: string = ''; async login(username: string, password: string) { const response = await fetch('/api/user/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); this.accessToken = data.token; this.refreshToken = data.refreshToken; // Always present now return data; } async makeAuthenticatedRequest(url: string, options: RequestInit = {}) { const headers = { 'Authorization': `Bearer ${this.accessToken}`, ...options.headers }; let response = await fetch(url, { ...options, headers }); // Automatically handle token refresh (tokens updated in response headers) if (response.headers.get('X-Token-Refreshed') === 'true') { const newAccessToken = response.headers.get('X-New-Access-Token'); const newRefreshToken = response.headers.get('X-New-Refresh-Token'); if (newAccessToken) this.accessToken = newAccessToken; if (newRefreshToken) this.refreshToken = newRefreshToken; } return response; } // Optional: Manual refresh (usually not needed due to automatic refresh) async refreshTokens() { const response = await fetch('/api/user/refresh-token', { method: 'POST', headers: { 'X-Refresh-Token': this.refreshToken } }); if (response.ok) { const data = await response.json(); this.accessToken = data.accessToken; this.refreshToken = data.refreshToken; return true; } return false; } async logout() { await fetch('/api/user/logout', { method: 'POST', headers: { 'Authorization': `Bearer ${this.accessToken}` } }); this.accessToken = ''; this.refreshToken = ''; } } ``` ### React Hook Example ```typescript import { useState, useCallback } from 'react'; export const useAuth = () => { const [accessToken, setAccessToken] = useState(''); const [refreshToken, setRefreshToken] = useState(''); const login = useCallback(async (username: string, password: string) => { const response = await fetch('/api/user/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); setAccessToken(data.token); setRefreshToken(data.refreshToken); // Always present return data; }, []); const logout = useCallback(async () => { if (accessToken) { await fetch('/api/user/logout', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}` } }); } setAccessToken(''); setRefreshToken(''); }, [accessToken]); return { accessToken, refreshToken, login, logout }; }; ``` ## Security Considerations 1. **Token Blacklisting**: Logout tokens are blacklisted in Redis with TTL matching token expiration 2. **Short-lived Access Tokens**: 30-minute expiry reduces exposure window 3. **Secure Cookies**: httpOnly, secure, sameSite attributes for cookie-based auth 4. **Token Rotation**: Refresh tokens are rotated on each refresh 5. **Environment-specific Secrets**: Different secrets for access and refresh tokens ## Migration Guide ### From Single Token to Refresh Token System Since this is a new implementation, all clients should expect: 1. **Login Response**: Always includes both `token` (access) and `refreshToken` 2. **Token Storage**: Store both tokens securely 3. **API Requests**: Use access token in Authorization header 4. **Automatic Refresh**: Tokens refresh automatically - just watch for response headers 5. **Logout**: Call logout endpoint to invalidate tokens **Key Point**: Manual refresh is optional since automatic refresh handles token renewal seamlessly. **No backward compatibility needed** - this is the only authentication method. ### Testing ```bash # Login and get tokens curl -X POST http://localhost:3000/api/user/login \ -H "Content-Type: application/json" \ -d '{"username": "test@example.com", "password": "password"}' # Use access token curl -X GET http://localhost:3000/api/user/profile \ -H "Authorization: Bearer " # Refresh tokens curl -X POST http://localhost:3000/api/user/refresh-token \ -H "X-Refresh-Token: " # Logout curl -X POST http://localhost:3000/api/user/logout \ -H "Authorization: Bearer " ```