No articles found
Try different keywords or browse our categories
Fix: JWT token invalid or expired error - Complete Guide
Complete guide to fix JWT token invalid or expired errors. Learn how to handle JWT authentication issues with practical solutions, token refresh strategies, and best practices for secure applications.
The ‘JWT token invalid or expired’ error is a common authentication issue that occurs when JSON Web Tokens (JWTs) used for securing API endpoints become invalid or expire. This error typically happens when the token has exceeded its expiration time, has been tampered with, or is malformed. JWT tokens are widely used for stateless authentication in modern web applications, and understanding how to handle these errors is crucial for maintaining secure and user-friendly applications.
This comprehensive guide explains what causes this error, why it happens, and provides multiple solutions to fix it in your applications with clean code examples and directory structure.
What is the JWT Token Invalid or Expired Error?
The “JWT token invalid or expired” error occurs when:
- The JWT token has exceeded its expiration time (exp claim)
- The token signature doesn’t match the payload
- The token has been tampered with or corrupted
- The token format is invalid or malformed
- The secret key used for signing doesn’t match
- The token issuer doesn’t match expected value
- The audience claim doesn’t match expected value
- The token has been revoked or blacklisted
Common Error Manifestations:
jwt expirederror messageinvalid tokenerror messagejwt malformederror messageinvalid signatureerror message- HTTP 401 Unauthorized responses
- Authentication failures in API calls
- Session timeouts for authenticated users
- Invalid credentials errors
Understanding the Problem
This error typically occurs due to:
- Token expiration time limits
- Incorrect token validation implementation
- Secret key mismatches between client and server
- Malformed token structures
- Clock synchronization issues between client and server
- Token storage and retrieval problems
- Network latency affecting token timing
- Improper token refresh mechanisms
Why This Error Happens:
JWT tokens contain an expiration time (exp claim) that defines when the token becomes invalid. When this time is reached, the server rejects the token. Additionally, JWTs include a digital signature that ensures the token hasn’t been tampered with. If the signature verification fails, the token is considered invalid.
Solution 1: Proper Token Validation
The first step is to implement proper token validation with comprehensive error handling.
❌ Without Proper Validation:
// ❌ Basic token validation without proper error handling
const jwt = require('jsonwebtoken');
function validateToken(token) {
// ❌ This will throw an error if token is invalid/expired
return jwt.verify(token, process.env.JWT_SECRET);
}
✅ With Proper Validation:
Server-Side Token Validation:
// ✅ Proper token validation with comprehensive error handling
const jwt = require('jsonwebtoken');
function validateToken(token) {
try {
// ✅ Verify token with proper error handling
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// ✅ Additional validation checks
if (!decoded.exp || decoded.exp < Math.floor(Date.now() / 1000)) {
throw new Error('Token has expired');
}
return {
valid: true,
decoded: decoded,
error: null
};
} catch (error) {
// ✅ Handle different types of JWT errors
let errorMessage = 'Invalid token';
if (error.name === 'TokenExpiredError') {
errorMessage = 'Token has expired';
} else if (error.name === 'JsonWebTokenError') {
errorMessage = 'Invalid token format';
} else if (error.name === 'NotBeforeError') {
errorMessage = 'Token not active yet';
}
return {
valid: false,
decoded: null,
error: errorMessage
};
}
}
// ✅ Express middleware for token validation
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
error: 'Access token required',
message: 'Authorization header missing'
});
}
const validationResult = validateToken(token);
if (!validationResult.valid) {
return res.status(403).json({
error: 'Forbidden',
message: validationResult.error
});
}
req.user = validationResult.decoded;
next();
}
Enhanced Token Validation:
// ✅ Enhanced token validation with additional security checks
const jwt = require('jsonwebtoken');
class JwtValidator {
constructor(secret, options = {}) {
this.secret = secret;
this.options = {
algorithms: ['HS256'],
clockTolerance: 30, // 30 seconds tolerance
...options
};
}
validate(token) {
try {
// ✅ Validate with options
const decoded = jwt.verify(token, this.secret, this.options);
// ✅ Additional security checks
this.validateClaims(decoded);
return {
valid: true,
decoded: decoded,
error: null
};
} catch (error) {
return this.handleError(error);
}
}
validateClaims(decoded) {
// ✅ Validate issuer if present
if (decoded.iss && decoded.iss !== process.env.TOKEN_ISSUER) {
throw new Error('Invalid issuer');
}
// ✅ Validate audience if present
if (decoded.aud && decoded.aud !== process.env.TOKEN_AUDIENCE) {
throw new Error('Invalid audience');
}
// ✅ Validate subject if present
if (decoded.sub && !this.isValidSubject(decoded.sub)) {
throw new Error('Invalid subject');
}
}
isValidSubject(subject) {
// ✅ Implement subject validation logic
return true; // Simplified for example
}
handleError(error) {
let errorMessage = 'Invalid token';
switch (error.name) {
case 'TokenExpiredError':
errorMessage = 'Token has expired';
break;
case 'JsonWebTokenError':
errorMessage = 'Invalid token format';
break;
case 'NotBeforeError':
errorMessage = 'Token not active yet';
break;
default:
errorMessage = error.message || 'Token validation failed';
}
return {
valid: false,
decoded: null,
error: errorMessage
};
}
}
// ✅ Usage
const validator = new JwtValidator(process.env.JWT_SECRET);
const result = validator.validate(token);
Solution 2: Token Refresh Mechanisms
❌ Without Refresh Mechanism:
// ❌ No token refresh mechanism
const authenticateUser = async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!validateToken(token).valid) {
return res.status(401).json({ error: 'Token expired' }); // ❌ User has to login again
}
// Continue with request
};
✅ With Refresh Mechanism:
Token Refresh Implementation:
// ✅ Token refresh mechanism implementation
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class TokenManager {
constructor() {
this.accessTokenSecret = process.env.ACCESS_TOKEN_SECRET;
this.refreshTokenSecret = process.env.REFRESH_TOKEN_SECRET;
}
// ✅ Generate access token (short-lived)
generateAccessToken(payload, expiresIn = '15m') {
return jwt.sign(payload, this.accessTokenSecret, { expiresIn });
}
// ✅ Generate refresh token (long-lived)
generateRefreshToken(payload, expiresIn = '7d') {
return jwt.sign(payload, this.refreshTokenSecret, { expiresIn });
}
// ✅ Validate access token
validateAccessToken(token) {
try {
return jwt.verify(token, this.accessTokenSecret);
} catch (error) {
return null;
}
}
// ✅ Validate refresh token
validateRefreshToken(token) {
try {
return jwt.verify(token, this.refreshTokenSecret);
} catch (error) {
return null;
}
}
// ✅ Refresh access token using refresh token
async refreshAccessToken(refreshToken) {
const refreshTokenPayload = this.validateRefreshToken(refreshToken);
if (!refreshTokenPayload) {
throw new Error('Invalid refresh token');
}
// ✅ Generate new access token
const newAccessToken = this.generateAccessToken({
userId: refreshTokenPayload.userId,
email: refreshTokenPayload.email
});
return {
accessToken: newAccessToken,
refreshToken: refreshToken // Return same refresh token
};
}
}
// ✅ Express route for token refresh
const tokenManager = new TokenManager();
app.post('/refresh-token', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token required' });
}
try {
const tokens = await tokenManager.refreshAccessToken(refreshToken);
res.json({
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken
});
} catch (error) {
res.status(403).json({ error: 'Invalid refresh token' });
}
});
Client-Side Token Refresh:
// ✅ Client-side token refresh implementation
class AuthService {
constructor() {
this.tokenManager = new TokenManager();
this.pendingRefresh = null;
}
// ✅ Get access token with automatic refresh if needed
async getValidAccessToken() {
const token = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
if (!token) {
throw new Error('No access token found');
}
// ✅ Check if token is expired
const isExpired = this.isTokenExpired(token);
if (isExpired && refreshToken) {
// ✅ Refresh token if expired
return await this.refreshTokens(refreshToken);
}
return token;
}
// ✅ Check if token is expired
isTokenExpired(token) {
try {
const payload = jwt.decode(token);
if (!payload.exp) return false;
const currentTime = Math.floor(Date.now() / 1000);
return payload.exp < currentTime;
} catch (error) {
return true; // Consider invalid tokens as expired
}
}
// ✅ Refresh tokens
async refreshTokens(refreshToken) {
// ✅ Prevent multiple simultaneous refresh requests
if (this.pendingRefresh) {
return await this.pendingRefresh;
}
this.pendingRefresh = this.performTokenRefresh(refreshToken);
try {
const result = await this.pendingRefresh;
return result.accessToken;
} finally {
this.pendingRefresh = null;
}
}
// ✅ Perform actual token refresh
async performTokenRefresh(refreshToken) {
try {
const response = await fetch('/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refreshToken })
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const tokens = await response.json();
// ✅ Update stored tokens
localStorage.setItem('accessToken', tokens.accessToken);
localStorage.setItem('refreshToken', tokens.refreshToken);
return tokens;
} catch (error) {
// ✅ Clear invalid tokens
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
throw error;
}
}
// ✅ Logout - clear tokens
logout() {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
}
}
Solution 3: Frontend Implementation
❌ Without Proper Frontend Handling:
// ❌ Basic fetch without token handling
async function apiCall(url) {
const token = localStorage.getItem('token');
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.status === 401) {
// ❌ Just redirect to login without token refresh
window.location.href = '/login';
}
return response.json();
}
✅ With Proper Frontend Handling:
Frontend API Service:
// ✅ Frontend API service with proper JWT handling
class ApiService {
constructor() {
this.authService = new AuthService();
this.baseURL = process.env.REACT_APP_API_URL || '/api';
}
// ✅ Make authenticated request with automatic token refresh
async request(endpoint, options = {}) {
try {
// ✅ Get valid access token
const token = await this.authService.getValidAccessToken();
const config = {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
...options.headers
},
...options
};
const response = await fetch(`${this.baseURL}${endpoint}`, config);
// ✅ Handle 401 errors (token might have expired between checks)
if (response.status === 401) {
// ✅ Try to refresh token and retry request
const newToken = await this.authService.getValidAccessToken();
// ✅ Retry request with new token
const retryConfig = {
...config,
headers: {
...config.headers,
'Authorization': `Bearer ${newToken}`
}
};
const retryResponse = await fetch(`${this.baseURL}${endpoint}`, retryConfig);
if (retryResponse.status === 401) {
// ✅ If retry fails, redirect to login
this.handleAuthFailure();
throw new Error('Authentication failed');
}
return await retryResponse.json();
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
// ✅ Handle authentication failure
handleAuthFailure() {
// ✅ Clear tokens and redirect to login
this.authService.logout();
window.location.href = '/login';
}
// ✅ Convenience methods
async get(endpoint) {
return this.request(endpoint, { method: 'GET' });
}
async post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
async delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
// ✅ Initialize API service
const apiService = new ApiService();
React Hook for JWT Handling:
// ✅ React hook for JWT authentication
import { useState, useEffect, useContext, createContext } from 'react';
const AuthContext = createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [authService] = useState(() => new AuthService());
useEffect(() => {
// ✅ Check authentication status on app load
checkAuthStatus();
}, []);
const checkAuthStatus = async () => {
try {
const token = localStorage.getItem('accessToken');
if (token && !authService.isTokenExpired(token)) {
// ✅ Decode user info from token
const payload = jwt.decode(token);
setUser({
id: payload.userId,
email: payload.email,
exp: payload.exp
});
}
} catch (error) {
console.error('Auth status check failed:', error);
} finally {
setLoading(false);
}
};
const login = async (credentials) => {
try {
const response = await fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
if (!response.ok) {
throw new Error('Login failed');
}
const { accessToken, refreshToken } = await response.json();
// ✅ Store tokens
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
// ✅ Update user state
const payload = jwt.decode(accessToken);
setUser({
id: payload.userId,
email: payload.email,
exp: payload.exp
});
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
};
const logout = () => {
authService.logout();
setUser(null);
};
const value = {
user,
login,
logout,
loading,
isAuthenticated: !!user
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
Solution 4: Backend Implementation
❌ Without Proper Backend Handling:
// ❌ Basic JWT middleware without refresh
const jwt = require('jsonwebtoken');
app.use((req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' }); // ❌ No refresh option
}
});
✅ With Proper Backend Handling:
Express.js JWT Middleware:
// ✅ Comprehensive JWT middleware with refresh support
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
// ✅ Rate limiting for authentication endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 requests per windowMs
message: 'Too many authentication attempts, please try again later.'
});
// ✅ JWT middleware with comprehensive error handling
const jwtMiddleware = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
error: 'Access token required',
message: 'Authorization header missing'
});
}
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
// ✅ Handle specific JWT errors
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired',
message: 'Please refresh your token',
code: 'TOKEN_EXPIRED'
});
} else if (err.name === 'JsonWebTokenError') {
return res.status(401).json({
error: 'Invalid token',
message: 'Token format is invalid',
code: 'INVALID_TOKEN'
});
} else if (err.name === 'NotBeforeError') {
return res.status(401).json({
error: 'Token not active',
message: 'Token is not active yet',
code: 'TOKEN_NOT_ACTIVE'
});
}
return res.status(401).json({
error: 'Invalid token',
message: 'Token validation failed',
code: 'VALIDATION_FAILED'
});
}
req.user = decoded;
next();
});
};
// ✅ Refresh token endpoint with security measures
app.post('/refresh-token', authLimiter, async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({
error: 'Refresh token required',
message: 'Refresh token is missing'
});
}
try {
// ✅ Verify refresh token
const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
// ✅ In a real application, you would check if the refresh token is still valid
// ✅ and hasn't been revoked (stored in database/blacklist)
// ✅ Generate new access token
const newAccessToken = jwt.sign(
{ userId: decoded.userId, email: decoded.email },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// ✅ Generate new refresh token (optional - rotate refresh tokens)
const newRefreshToken = jwt.sign(
{ userId: decoded.userId, email: decoded.email },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);
res.json({
accessToken: newAccessToken,
refreshToken: newRefreshToken
});
} catch (error) {
res.status(403).json({
error: 'Invalid refresh token',
message: 'Refresh token is invalid or has expired'
});
}
});
// ✅ Protected route using JWT middleware
app.get('/protected', jwtMiddleware, (req, res) => {
res.json({
message: 'Protected route accessed successfully',
user: req.user
});
});
Advanced JWT Configuration:
// ✅ Advanced JWT configuration with security enhancements
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class SecureJwtManager {
constructor() {
this.accessSecret = this.generateSecret(process.env.JWT_ACCESS_SECRET);
this.refreshSecret = this.generateSecret(process.env.JWT_REFRESH_SECRET);
}
// ✅ Generate secure secret if not provided
generateSecret(envSecret) {
if (envSecret) {
return envSecret;
}
// ✅ Generate a random secret for development
return crypto.randomBytes(64).toString('hex');
}
// ✅ Create access token with enhanced security
createAccessToken(payload, options = {}) {
const defaultOptions = {
expiresIn: '15m', // Short-lived access token
algorithm: 'RS256', // Use RS256 for asymmetric encryption if possible
issuer: process.env.TOKEN_ISSUER || 'your-app',
audience: process.env.TOKEN_AUDIENCE || 'your-app-users'
};
return jwt.sign(
{ ...payload, jti: this.generateJti() }, // Add unique token ID
this.accessSecret,
{ ...defaultOptions, ...options }
);
}
// ✅ Create refresh token with enhanced security
createRefreshToken(payload, options = {}) {
const defaultOptions = {
expiresIn: '7d', // Longer-lived refresh token
issuer: process.env.TOKEN_ISSUER || 'your-app',
audience: process.env.TOKEN_AUDIENCE || 'your-app-users'
};
return jwt.sign(
{ ...payload, jti: this.generateJti() },
this.refreshSecret,
{ ...defaultOptions, ...options }
);
}
// ✅ Generate unique token ID
generateJti() {
return crypto.randomBytes(16).toString('hex');
}
// ✅ Verify token with enhanced security
verifyToken(token, tokenType = 'access') {
const secret = tokenType === 'access' ? this.accessSecret : this.refreshSecret;
try {
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
clockTolerance: 30, // 30 seconds tolerance for clock skew
issuer: process.env.TOKEN_ISSUER || 'your-app',
audience: process.env.TOKEN_AUDIENCE || 'your-app-users'
});
return { valid: true, decoded };
} catch (error) {
return { valid: false, error: this.mapJwtError(error) };
}
}
// ✅ Map JWT errors to user-friendly messages
mapJwtError(error) {
switch (error.name) {
case 'TokenExpiredError':
return { code: 'EXPIRED', message: 'Token has expired' };
case 'JsonWebTokenError':
return { code: 'INVALID_FORMAT', message: 'Invalid token format' };
case 'NotBeforeError':
return { code: 'NOT_ACTIVE', message: 'Token is not active yet' };
default:
return { code: 'UNKNOWN', message: 'Token validation failed' };
}
}
// ✅ Blacklist token (in a real app, store in Redis/database)
blacklistToken(token, expiryTime) {
// ✅ Implementation would store token in blacklist
// ✅ until its original expiry time
}
}
Solution 5: Error Handling and Recovery
❌ Without Proper Error Recovery:
// ❌ Basic error handling
try {
const decoded = jwt.verify(token, secret);
// Process request
} catch (error) {
res.status(401).json({ error: 'Invalid token' }); // ❌ No recovery options
}
✅ With Proper Error Recovery:
Comprehensive Error Handling:
// ✅ Comprehensive JWT error handling and recovery
class JwtErrorHandler {
constructor(authService) {
this.authService = authService;
}
// ✅ Handle JWT errors with appropriate recovery strategies
async handleJwtError(req, res, error, originalRequest) {
const errorInfo = this.analyzeJwtError(error);
switch (errorInfo.code) {
case 'EXPIRED':
return await this.handleExpiredToken(req, res, originalRequest);
case 'INVALID_FORMAT':
return this.handleInvalidFormat(req, res, errorInfo);
case 'INVALID_SIGNATURE':
return this.handleInvalidSignature(req, res, errorInfo);
default:
return this.handleGenericError(req, res, errorInfo);
}
}
// ✅ Analyze JWT error for appropriate handling
analyzeJwtError(error) {
if (error.name === 'TokenExpiredError') {
return { code: 'EXPIRED', message: error.message, original: error };
} else if (error.name === 'JsonWebTokenError') {
if (error.message.includes('invalid signature')) {
return { code: 'INVALID_SIGNATURE', message: error.message, original: error };
} else if (error.message.includes('malformed')) {
return { code: 'INVALID_FORMAT', message: error.message, original: error };
}
} else if (error.name === 'NotBeforeError') {
return { code: 'NOT_ACTIVE', message: error.message, original: error };
}
return { code: 'UNKNOWN', message: error.message, original: error };
}
// ✅ Handle expired token with refresh attempt
async handleExpiredToken(req, res, originalRequest) {
const refreshToken = this.extractRefreshToken(req);
if (!refreshToken) {
return res.status(401).json({
error: 'Token expired',
message: 'Access token expired and no refresh token available',
action: 'LOGIN_REQUIRED'
});
}
try {
// ✅ Attempt to refresh token
const newTokens = await this.authService.refreshTokens(refreshToken);
// ✅ Retry original request with new token
const retryResult = await this.retryOriginalRequest(
originalRequest,
newTokens.accessToken
);
// ✅ Return success with new token
res.set('Authorization', `Bearer ${newTokens.accessToken}`);
return res.status(retryResult.status).json(retryResult.data);
} catch (refreshError) {
// ✅ Refresh failed, require login
return res.status(401).json({
error: 'Token refresh failed',
message: 'Session expired, please log in again',
action: 'LOGIN_REQUIRED'
});
}
}
// ✅ Extract refresh token from request
extractRefreshToken(req) {
// ✅ Try different locations for refresh token
return req.body.refreshToken ||
req.cookies.refreshToken ||
req.headers['x-refresh-token'];
}
// ✅ Retry original request with new token
async retryOriginalRequest(originalRequest, newToken) {
// ✅ Implementation depends on your request handling
// ✅ This is a simplified example
return {
status: 200,
data: { message: 'Request retried successfully' }
};
}
// ✅ Handle invalid format error
handleInvalidFormat(req, res, errorInfo) {
return res.status(400).json({
error: 'Invalid token format',
message: 'The provided token format is invalid',
action: 'CORRECT_TOKEN_FORMAT'
});
}
// ✅ Handle invalid signature error
handleInvalidSignature(req, res, errorInfo) {
return res.status(401).json({
error: 'Invalid token signature',
message: 'Token signature verification failed',
action: 'LOGIN_REQUIRED'
});
}
// ✅ Handle generic error
handleGenericError(req, res, errorInfo) {
return res.status(401).json({
error: 'Token validation failed',
message: errorInfo.message,
action: 'LOGIN_REQUIRED'
});
}
}
Working Code Examples
Complete Express.js Implementation:
// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const app = express();
// ✅ Middleware
app.use(cors());
app.use(express.json());
// ✅ Rate limiting for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5,
message: 'Too many authentication attempts, please try again later.'
});
// ✅ JWT Secrets (use environment variables in production)
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'your-access-secret';
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || 'your-refresh-secret';
// ✅ In-memory refresh token store (use Redis/database in production)
let refreshTokens = [];
// ✅ Login endpoint
app.post('/login', authLimiter, (req, res) => {
const { username, password } = req.body;
// ✅ Validate credentials (implement your own validation)
if (username === 'admin' && password === 'password') {
const userId = 1; // Get from database in real app
// ✅ Generate tokens
const accessToken = jwt.sign(
{ userId, username },
ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId, username },
REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);
// ✅ Store refresh token
refreshTokens.push(refreshToken);
res.json({
accessToken,
refreshToken,
user: { id: userId, username }
});
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
// ✅ Token refresh endpoint
app.post('/token', (req, res) => {
const { token } = req.body;
if (!token) {
return res.sendStatus(401);
}
if (!refreshTokens.includes(token)) {
return res.sendStatus(403);
}
jwt.verify(token, REFRESH_TOKEN_SECRET, (err, user) => {
if (err) {
return res.sendStatus(403);
}
const accessToken = jwt.sign(
{ userId: user.userId, username: user.username },
ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);
res.json({ accessToken });
});
});
// ✅ Logout endpoint
app.delete('/logout', (req, res) => {
const { token } = req.body;
refreshTokens = refreshTokens.filter(t => t !== token);
res.sendStatus(204);
});
// ✅ Middleware to authenticate token
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, ACCESS_TOKEN_SECRET, (err, decoded) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired',
message: 'Please refresh your token'
});
}
return res.status(403).json({ error: 'Invalid token' });
}
req.user = decoded;
next();
});
};
// ✅ Protected route
app.get('/profile', authenticateToken, (req, res) => {
res.json({ message: 'Profile data', user: req.user });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Complete Frontend Implementation:
// auth.js
class AuthManager {
constructor() {
this.accessToken = localStorage.getItem('accessToken');
this.refreshToken = localStorage.getItem('refreshToken');
this.tokenExpiry = localStorage.getItem('tokenExpiry');
}
// ✅ Check if token is expired
isTokenExpired() {
if (!this.tokenExpiry) return true;
return Date.now() >= parseInt(this.tokenExpiry);
}
// ✅ Get valid access token (with refresh if needed)
async getValidAccessToken() {
if (!this.accessToken) {
throw new Error('No access token available');
}
if (this.isTokenExpired()) {
await this.refreshAccessToken();
}
return this.accessToken;
}
// ✅ Refresh access token
async refreshAccessToken() {
if (!this.refreshToken) {
throw new Error('No refresh token available');
}
try {
const response = await fetch('/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ token: this.refreshToken })
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const { accessToken } = await response.json();
this.accessToken = accessToken;
localStorage.setItem('accessToken', accessToken);
// ✅ Update expiry time (assuming 15 minute tokens)
const expiryTime = Date.now() + (15 * 60 * 1000);
this.tokenExpiry = expiryTime.toString();
localStorage.setItem('tokenExpiry', this.tokenExpiry.toString());
} catch (error) {
// ✅ Clear invalid tokens
this.clearTokens();
throw error;
}
}
// ✅ Store tokens
storeTokens(accessToken, refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
// ✅ Calculate expiry time
const payload = JSON.parse(atob(accessToken.split('.')[1]));
const expiryTime = payload.exp * 1000; // Convert to milliseconds
this.tokenExpiry = expiryTime.toString();
localStorage.setItem('tokenExpiry', this.tokenExpiry);
}
// ✅ Clear tokens
clearTokens() {
this.accessToken = null;
this.refreshToken = null;
this.tokenExpiry = null;
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('tokenExpiry');
}
// ✅ Make authenticated request
async authenticatedFetch(url, options = {}) {
const token = await this.getValidAccessToken();
const config = {
headers: {
'Authorization': `Bearer ${token}`,
...options.headers
},
...options
};
let response = await fetch(url, config);
// ✅ Handle 401 errors (might need token refresh)
if (response.status === 401) {
try {
await this.refreshAccessToken();
const newToken = await this.getValidAccessToken();
// ✅ Retry request with new token
config.headers.Authorization = `Bearer ${newToken}`;
response = await fetch(url, config);
} catch (refreshError) {
// ✅ Refresh failed, redirect to login
window.location.href = '/login';
throw refreshError;
}
}
return response;
}
}
// ✅ Initialize auth manager
const authManager = new AuthManager();
// ✅ Export for use in other modules
window.AuthManager = authManager;
Best Practices for JWT Security
1. Use Strong Secrets
// ✅ Use strong, randomly generated secrets
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || crypto.randomBytes(64).toString('hex');
2. Short-Lived Access Tokens
// ✅ Use short-lived access tokens (15-30 minutes)
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
3. Secure Refresh Tokens
// ✅ Store refresh tokens securely and rotate them
// ✅ Use HttpOnly cookies for refresh tokens when possible
4. Validate Claims
// ✅ Always validate token claims
const decoded = jwt.verify(token, secret, {
issuer: 'your-app',
audience: 'your-users',
clockTolerance: 30
});
5. Handle Token Revocation
// ✅ Implement token blacklisting for logout and compromised tokens
// ✅ Use Redis or database for token revocation lists
Debugging Steps
Step 1: Decode the JWT Token
# ✅ Use jwt.io or command line tools to decode the token
# Check if the token structure is valid
# Verify the expiration time
Step 2: Check Secret Keys
// ✅ Verify that the secret key matches between client and server
console.log('Secret matches:', process.env.JWT_SECRET === expectedSecret);
Step 3: Verify Token Format
// ✅ Check if the token has the correct format: header.payload.signature
const parts = token.split('.');
console.log('Token has 3 parts:', parts.length === 3);
Step 4: Test Token Expiration
// ✅ Check if the token has expired
const payload = jwt.decode(token);
const isExpired = payload.exp < Math.floor(Date.now() / 1000);
Common Mistakes to Avoid
1. Using Weak Secrets
// ❌ Don't use weak or predictable secrets
const secret = 'password'; // ❌ Too weak
// ✅ Use strong, random secrets
const secret = crypto.randomBytes(64).toString('hex'); // ✅ Much stronger
2. Long-Lived Access Tokens
// ❌ Don't use long-lived access tokens
const token = jwt.sign(payload, secret, { expiresIn: '30d' }); // ❌ Too long
// ✅ Use short-lived access tokens
const token = jwt.sign(payload, secret, { expiresIn: '15m' }); // ✅ Better
3. Not Handling Token Refresh
// ❌ Don't ignore token expiration
// ✅ Always implement token refresh mechanisms
4. Storing Tokens Insecurely
// ❌ Don't store sensitive tokens in plain localStorage
localStorage.setItem('refreshToken', token); // ❌ Vulnerable to XSS
// ✅ Use HttpOnly cookies for refresh tokens when possible
// ✅ Or implement proper XSS protection
Performance Considerations
1. Efficient Token Validation
// ✅ Cache token validation results when appropriate
// ✅ Use synchronous validation for better performance
2. Minimize Token Size
// ✅ Keep token payloads small
// ✅ Only include necessary claims
3. Optimize Refresh Token Storage
// ✅ Use efficient storage for refresh tokens
// ✅ Consider Redis for high-performance applications
Security Considerations
1. Protect Against Timing Attacks
// ✅ Use constant-time comparison for token validation
// ✅ Don't leak information about token validity
2. Implement Proper Error Messages
// ✅ Don't expose sensitive information in error messages
// ✅ Use generic error messages for security
3. Secure Token Transmission
// ✅ Always use HTTPS for token transmission
// ✅ Never transmit tokens over HTTP
Testing JWT Implementation
1. Unit Tests for Token Validation
// ✅ Test token validation with various scenarios
test('should validate valid token', () => {
const token = jwt.sign({ id: 1 }, secret, { expiresIn: '1h' });
const result = validateToken(token);
expect(result.valid).toBe(true);
});
2. Integration Tests for Token Refresh
// ✅ Test token refresh functionality
test('should refresh expired token', async () => {
// Test refresh token functionality
});
3. Security Tests
// ✅ Test security aspects of JWT implementation
// ✅ Test token tampering protection
// ✅ Test expiration handling
Alternative Solutions
1. Session-Based Authentication
// ✅ Consider session-based auth for some use cases
// ✅ Better for applications with frequent server communication
2. OAuth 2.0 Integration
// ✅ Consider OAuth 2.0 for third-party authentication
// ✅ Better for applications integrating with external services
3. Custom Token Validation
// ✅ Implement custom validation logic as needed
// ✅ Add additional security layers when required
Migration Checklist
- Implement proper token validation with error handling
- Add token refresh mechanisms
- Secure token storage and transmission
- Test token expiration and refresh workflows
- Implement proper error handling and user feedback
- Add security measures against common attacks
- Update documentation for team members
- Test with various token states (valid, expired, invalid)
Conclusion
The ‘JWT token invalid or expired’ error is a common but manageable authentication issue that occurs when JSON Web Tokens become invalid or expire. By following the solutions provided in this guide—whether through proper token validation, refresh mechanisms, secure implementation, or comprehensive error handling—you can create robust and secure authentication systems.
The key is to implement proper token validation, use short-lived access tokens with refresh mechanisms, handle errors gracefully, and maintain security best practices. With proper implementation of these patterns, your applications will provide a seamless authentication experience while maintaining security.
Remember to always use strong secrets, implement token refresh mechanisms, handle errors gracefully, and test thoroughly to create secure and user-friendly applications that leverage the full power of JWT authentication.
Related Articles
Fix: 401 Unauthorized Error - Complete Guide to Authentication Issues
Complete guide to fix 401 Unauthorized errors. Learn how to resolve authentication issues with practical solutions, token management, and best practices for secure API communication.
How to Fix: API Key Not Working error - Full Tutorial
Complete guide to fix API key not working errors. Learn how to resolve authentication issues with practical solutions, key management, and best practices for secure API communication.
How to Fix: Google OAuth redirect_uri_mismatch Error - Full Tutorial
Complete guide to fix Google OAuth redirect_uri_mismatch errors. Learn how to resolve OAuth redirect URI issues with practical solutions, configuration fixes, and best practices for secure authentication.