JWT expiration time management and refresh mechanism are important parts of ensuring security and user experience. Here's a detailed implementation:
Expiration Time Settings
1. Basic Expiration Time Settings
javascriptconst jwt = require('jsonwebtoken'); // Use expiresIn parameter const token = jwt.sign(payload, 'secret', { expiresIn: '1h' // 1 hour // expiresIn: '30m' // 30 minutes // expiresIn: '2d' // 2 days // expiresIn: '7d' // 7 days // expiresIn: 3600 // 3600 seconds (1 hour) }); // Or use exp claim const exp = Math.floor(Date.now() / 1000) + (60 * 60); // Expire in 1 hour const token = jwt.sign({ ...payload, exp }, 'secret');
2. Recommended Expiration Time Strategy
- Access Token: 15-30 minutes
- Refresh Token: 7-30 days
- Remember Me Token: 30-90 days
Refresh Token Mechanism Implementation
1. Dual Token Architecture
javascriptconst crypto = require('crypto'); const jwt = require('jsonwebtoken'); // Store Refresh Tokens (use Redis in production) const refreshTokens = new Map(); // Generate Access Token function generateAccessToken(userId) { return jwt.sign( { userId }, process.env.JWT_SECRET, { expiresIn: '15m', issuer: 'your-app.com', audience: 'your-api' } ); } // Generate Refresh Token function generateRefreshToken() { return crypto.randomBytes(40).toString('hex'); } // Login endpoint app.post('/auth/login', async (req, res) => { const { username, password } = req.body; // Verify user credentials const user = await validateUser(username, password); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } // Generate tokens const accessToken = generateAccessToken(user.id); const refreshToken = generateRefreshToken(); // Store refresh token (with expiration) refreshTokens.set(refreshToken, { userId: user.id, expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days }); res.json({ accessToken, refreshToken, expiresIn: 900 // 15 minutes (seconds) }); }); // Refresh token endpoint app.post('/auth/refresh', (req, res) => { const { refreshToken } = req.body; if (!refreshToken) { return res.status(400).json({ error: 'Refresh token required' }); } const tokenData = refreshTokens.get(refreshToken); if (!tokenData) { return res.status(401).json({ error: 'Invalid refresh token' }); } // Check if expired if (Date.now() > tokenData.expiresAt) { refreshTokens.delete(refreshToken); return res.status(401).json({ error: 'Refresh token expired' }); } // Generate new access token const newAccessToken = generateAccessToken(tokenData.userId); res.json({ accessToken: newAccessToken, expiresIn: 900 }); }); // Logout endpoint app.post('/auth/logout', (req, res) => { const { refreshToken } = req.body; refreshTokens.delete(refreshToken); res.json({ success: true }); });
2. Redis Storage Implementation (Recommended)
javascriptconst redis = require('redis'); const client = redis.createClient(); async function storeRefreshToken(refreshToken, userId, expiresIn = '7d') { await client.setex( `refresh:${refreshToken}`, 60 * 60 * 24 * 7, // 7 days (seconds) userId ); } async function getUserIdFromRefreshToken(refreshToken) { const userId = await client.get(`refresh:${refreshToken}`); return userId; } async function deleteRefreshToken(refreshToken) { await client.del(`refresh:${refreshToken}`); }
Automatic Refresh Mechanism
1. Frontend Automatic Refresh
javascript// Using Axios interceptors import axios from 'axios'; const api = axios.create({ baseURL: 'https://api.example.com' }); let isRefreshing = false; let refreshSubscribers = []; function subscribeTokenRefresh(callback) { refreshSubscribers.push(callback); } function onRefreshed(token) { refreshSubscribers.forEach(callback => callback(token)); refreshSubscribers = []; } api.interceptors.request.use(config => { const token = localStorage.getItem('accessToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); api.interceptors.response.use( response => response, async error => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { if (isRefreshing) { return new Promise(resolve => { subscribeTokenRefresh(token => { originalRequest.headers.Authorization = `Bearer ${token}`; resolve(api(originalRequest)); }); }); } originalRequest._retry = true; isRefreshing = true; try { const refreshToken = localStorage.getItem('refreshToken'); const response = await axios.post('/auth/refresh', { refreshToken }); const { accessToken } = response.data; localStorage.setItem('accessToken', accessToken); onRefreshed(accessToken); isRefreshing = false; originalRequest.headers.Authorization = `Bearer ${accessToken}`; return api(originalRequest); } catch (refreshError) { isRefreshing = false; localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; return Promise.reject(refreshError); } } return Promise.reject(error); } );
2. Scheduled Refresh
javascript// Refresh token 5 minutes before it expires function scheduleTokenRefresh() { const token = localStorage.getItem('accessToken'); if (!token) return; const decoded = jwt.decode(token); const expiresIn = decoded.exp * 1000 - Date.now(); const refreshBefore = 5 * 60 * 1000; // 5 minutes if (expiresIn > refreshBefore) { setTimeout(async () => { try { const refreshToken = localStorage.getItem('refreshToken'); const response = await axios.post('/auth/refresh', { refreshToken }); localStorage.setItem('accessToken', response.data.accessToken); scheduleTokenRefresh(); // Continue scheduling } catch (error) { console.error('Token refresh failed', error); } }, expiresIn - refreshBefore); } } // Start scheduled refresh after successful login scheduleTokenRefresh();
Advanced Features
1. Token Blacklist
javascriptconst redis = require('redis'); const client = redis.createClient(); // Add token to blacklist on logout async function logout(token) { const decoded = jwt.decode(token); const ttl = decoded.exp - Math.floor(Date.now() / 1000); if (ttl > 0) { await client.setex(`blacklist:${token}`, ttl, '1'); } } // Check blacklist during verification async function verifyToken(token) { const isBlacklisted = await client.exists(`blacklist:${token}`); if (isBlacklisted) { throw new Error('Token is blacklisted'); } return jwt.verify(token, process.env.JWT_SECRET); }
2. Token Version Control
javascript// Maintain tokenVersion field in user table function generateAccessToken(userId, tokenVersion) { return jwt.sign( { userId, tokenVersion }, process.env.JWT_SECRET, { expiresIn: '15m' } ); } // Check version during verification async function verifyToken(token) { const decoded = jwt.verify(token, process.env.JWT_SECRET); const user = await getUserById(decoded.userId); if (user.tokenVersion !== decoded.tokenVersion) { throw new Error('Token version mismatch'); } return decoded; } // Force logout all devices (increment version) async function revokeAllTokens(userId) { await incrementTokenVersion(userId); }
3. Sliding Expiration
javascript// Extend refresh token validity on each use app.post('/auth/refresh', async (req, res) => { const { refreshToken } = req.body; const tokenData = await getRefreshTokenData(refreshToken); // Extend refresh token validity await extendRefreshTokenExpiry(refreshToken, 7 * 24 * 60 * 60); // 7 days const newAccessToken = generateAccessToken(tokenData.userId); res.json({ accessToken: newAccessToken, expiresIn: 900 }); });
Best Practices
- Use short-lived Access Tokens: 15-30 minutes
- Use long-lived Refresh Tokens: 7-30 days
- Store Refresh Token in HttpOnly Cookie: Prevent XSS
- Implement Token Blacklist: Support active revocation
- Add Token Version Control: Support forced logout
- Log Token Usage: Monitor abnormal behavior
- Limit Refresh Token Usage: Prevent abuse
- Use HTTPS for Transmission: Prevent man-in-the-middle attacks
With proper expiration time management and refresh mechanisms, you can achieve a good balance between security and user experience.