JWT error handling is crucial for building robust authentication systems. Here are complete error handling strategies and implementation methods:
Common JWT Error Types
1. Token Format Error
javascriptconst jwt = require('jsonwebtoken'); function verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.name === 'JsonWebTokenError') { throw new Error('Invalid token format'); } throw error; } }
2. Token Expiration Error
javascriptfunction verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.name === 'TokenExpiredError') { throw new Error('Token has expired'); } throw error; } }
3. Signature Verification Error
javascriptfunction verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.message === 'invalid signature') { throw new Error('Invalid token signature'); } throw error; } }
4. Algorithm Error
javascriptfunction verifyToken(token) { try { return jwt.verify(token, SECRET_KEY, { algorithms: ['RS256'] }); } catch (error) { if (error.message === 'invalid algorithm') { throw new Error('Invalid token algorithm'); } throw error; } }
Unified Error Handling Middleware
Express Error Handling Middleware
javascriptconst jwt = require('jsonwebtoken'); // Authentication middleware function authMiddleware(req, res, next) { const authHeader = req.headers['authorization']; if (!authHeader) { return res.status(401).json({ error: 'Unauthorized', message: 'No token provided' }); } const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader; try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { handleJwtError(error, res); } } // JWT error handling function function handleJwtError(error, res) { const errorMap = { 'TokenExpiredError': { status: 401, code: 'TOKEN_EXPIRED', message: 'Token has expired' }, 'JsonWebTokenError': { status: 401, code: 'INVALID_TOKEN', message: 'Invalid token' }, 'NotBeforeError': { status: 401, code: 'TOKEN_NOT_ACTIVE', message: 'Token is not active yet' } }; const errorInfo = errorMap[error.name] || { status: 500, code: 'AUTH_ERROR', message: 'Authentication error' }; res.status(errorInfo.status).json({ error: errorInfo.code, message: errorInfo.message, timestamp: new Date().toISOString() }); } // Global error handling middleware app.use((err, req, res, next) => { console.error('Error:', err); if (err.name === 'JsonWebTokenError' || err.name === 'TokenExpiredError' || err.name === 'NotBeforeError') { return handleJwtError(err, res); } res.status(500).json({ error: 'INTERNAL_SERVER_ERROR', message: 'An unexpected error occurred' }); });
Error Logging
Structured Logging
javascriptconst winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'auth-errors.log' }) ] }); function logAuthError(error, req) { logger.error({ error: error.name, message: error.message, stack: error.stack, timestamp: new Date().toISOString(), ip: req.ip, userAgent: req.headers['user-agent'], path: req.path, method: req.method }); } // Usage example function authMiddleware(req, res, next) { try { const decoded = jwt.verify(token, SECRET_KEY); req.user = decoded; next(); } catch (error) { logAuthError(error, req); handleJwtError(error, res); } }
Token Refresh Error Handling
Error Handling for Token Refresh
javascriptasync function refreshToken(refreshToken) { try { // Verify refresh token const decoded = jwt.verify(refreshToken, REFRESH_SECRET); // Check if blacklisted const isBlacklisted = await checkBlacklist(refreshToken); if (isBlacklisted) { throw new Error('Token has been revoked'); } // Generate new access token const accessToken = jwt.sign( { userId: decoded.userId }, ACCESS_SECRET, { expiresIn: '15m' } ); return { accessToken, expiresIn: 900 }; } catch (error) { throw handleRefreshError(error); } } function handleRefreshError(error) { const errorMap = { 'TokenExpiredError': { code: 'REFRESH_TOKEN_EXPIRED', message: 'Refresh token has expired, please login again', action: 'LOGIN_REQUIRED' }, 'JsonWebTokenError': { code: 'INVALID_REFRESH_TOKEN', message: 'Invalid refresh token', action: 'LOGIN_REQUIRED' }, 'TokenRevokedError': { code: 'TOKEN_REVOKED', message: 'Token has been revoked', action: 'LOGIN_REQUIRED' } }; const errorInfo = errorMap[error.name] || { code: 'REFRESH_ERROR', message: 'Failed to refresh token', action: 'RETRY' }; return { ...errorInfo, originalError: error.message }; }
Frontend Error Handling
Axios Interceptor
javascriptimport axios from 'axios'; const api = axios.create({ baseURL: 'https://api.example.com' }); // Response interceptor api.interceptors.response.use( response => response, async error => { const originalRequest = error.config; // Handle 401 errors if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; const errorCode = error.response.data?.error; // Token expired, try refresh if (errorCode === 'TOKEN_EXPIRED') { try { const refreshToken = localStorage.getItem('refreshToken'); const response = await axios.post('/auth/refresh', { refreshToken }); const { accessToken } = response.data; localStorage.setItem('accessToken', accessToken); // Retry original request originalRequest.headers.Authorization = `Bearer ${accessToken}`; return api(originalRequest); } catch (refreshError) { // Refresh failed, redirect to login localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; return Promise.reject(refreshError); } } // Other 401 errors, redirect to login localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; } // Handle other errors return Promise.reject(error); } );
Error Monitoring and Alerting
Sentry Integration
javascriptconst Sentry = require('@sentry/node'); Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV }); function authMiddleware(req, res, next) { try { const decoded = jwt.verify(token, SECRET_KEY); req.user = decoded; next(); } catch (error) { // Send error to Sentry Sentry.captureException(error, { tags: { errorType: error.name, endpoint: req.path }, extra: { ip: req.ip, userAgent: req.headers['user-agent'] } }); handleJwtError(error, res); } }
Custom Error Classes
Create Custom Errors
javascriptclass AuthError extends Error { constructor(code, message, statusCode = 401) { super(message); this.name = 'AuthError'; this.code = code; this.statusCode = statusCode; } } class TokenExpiredError extends AuthError { constructor() { super('TOKEN_EXPIRED', 'Token has expired', 401); } } class InvalidTokenError extends AuthError { constructor() { super('INVALID_TOKEN', 'Invalid token', 401); } } class TokenRevokedError extends AuthError { constructor() { super('TOKEN_REVOKED', 'Token has been revoked', 401); } } // Use custom errors function verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.name === 'TokenExpiredError') { throw new TokenExpiredError(); } if (error.name === 'JsonWebTokenError') { throw new InvalidTokenError(); } throw new AuthError('AUTH_ERROR', 'Authentication failed'); } }
Error Recovery Strategies
Retry Mechanism
javascriptasync function verifyTokenWithRetry(token, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { // Only retry transient errors if (error.name === 'TokenExpiredError') { throw error; // Don't retry expired errors } if (i === maxRetries - 1) { throw error; } // Wait before retry await new Promise(resolve => setTimeout(resolve, 100 * (i + 1))); } } }
Best Practices
Error Handling Checklist
- Unified error format
- Detailed error logging
- Distinguish different error types
- Provide friendly error messages
- Implement error monitoring
- Set up error alerts
- Handle frontend errors
- Implement retry mechanism
- Use custom error classes
- Regularly analyze error logs
Error Handling Principles
- Don't expose sensitive info: Error messages shouldn't contain keys or internal implementation details
- Log complete context: Logs should include request info, user info, etc.
- Provide clear feedback: Users should know what happened and how to resolve it
- Monitor critical errors: Set up alerts for important errors
- Regularly analyze logs: Discover potential issues and attack patterns
With comprehensive error handling mechanisms, you can improve the robustness and user experience of JWT authentication systems.