search
Tutorials

Fix: Token expired error

Complete guide to fix 'Token expired' error. Learn how to implement token refresh, handle expiration, and manage authentication tokens effectively.

person By Gautam Sharma
calendar_today January 8, 2026
schedule 9 min read
Authentication JWT Tokens Expiration Error Fix Security

The ‘Token expired’ error occurs when an authentication token has exceeded its validity period and can no longer be used for authorization. This is a common issue in applications using time-limited authentication tokens.


How the Error Happens

This error typically occurs when:

  • JWT token has exceeded its expiration time (exp claim)
  • Session token has timed out
  • Access token lifetime has elapsed
  • Refresh token has also expired
  • Clock synchronization issues between client and server
  • Token wasn’t refreshed before expiration

Solution 1: Implement Token Refresh Mechanism

// ✅ Token refresh utility
class TokenManager {
    constructor() {
        this.accessToken = localStorage.getItem('accessToken');
        this.refreshToken = localStorage.getItem('refreshToken');
    }
    
    isTokenExpired(token) {
        try {
            const payload = jwt.decode(token);
            const currentTime = Math.floor(Date.now() / 1000);
            return payload.exp < currentTime;
        } catch (error) {
            return true; // If we can't decode, assume expired
        }
    }
    
    async refreshToken() {
        if (!this.refreshToken) {
            throw new Error('No refresh token available');
        }
        
        try {
            const response = await fetch('/api/refresh', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    refreshToken: this.refreshToken
                })
            });
            
            if (!response.ok) {
                throw new Error('Refresh token failed');
            }
            
            const data = await response.json();
            this.accessToken = data.accessToken;
            this.refreshToken = data.refreshToken;
            
            localStorage.setItem('accessToken', data.accessToken);
            localStorage.setItem('refreshToken', data.refreshToken);
            
            return data.accessToken;
        } catch (error) {
            // ✅ Clear invalid tokens
            this.clearTokens();
            throw error;
        }
    }
    
    clearTokens() {
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
        this.accessToken = null;
        this.refreshToken = null;
    }
}

Solution 2: Automatic Token Refresh in API Calls

// ✅ Axios interceptor for automatic token refresh
import axios from 'axios';

const api = axios.create({
    baseURL: process.env.REACT_APP_API_URL
});

let isRefreshing = false;
let failedQueue = [];

const processQueue = (error, token = null) => {
    failedQueue.forEach(prom => {
        if (error) {
            prom.reject(error);
        } else {
            prom.resolve(token);
        }
    });
    
    failedQueue = [];
};

api.interceptors.response.use(
    response => response,
    async error => {
        const originalRequest = error.config;
        
        if (error.response?.status === 401 && error.response.data.error === 'Token expired') {
            if (isRefreshing) {
                // ✅ Queue request while refresh is in progress
                return new Promise((resolve, reject) => {
                    failedQueue.push({ resolve, reject });
                }).then(token => {
                    originalRequest.headers['Authorization'] = 'Bearer ' + token;
                    return axios(originalRequest);
                }).catch(err => {
                    return Promise.reject(err);
                });
            }
            
            isRefreshing = true;
            
            try {
                const newToken = await refreshToken();
                processQueue(null, newToken);
                
                originalRequest.headers['Authorization'] = 'Bearer ' + newToken;
                return axios(originalRequest);
            } catch (refreshError) {
                processQueue(refreshError, null);
                // ✅ Redirect to login
                window.location.href = '/login';
                return Promise.reject(refreshError);
            } finally {
                isRefreshing = false;
            }
        }
        
        return Promise.reject(error);
    }
);

Solution 3: Frontend Token Expiration Handling

// ✅ React hook for token management
import { useState, useEffect } from 'react';

function useTokenExpiration() {
    const [isExpired, setIsExpired] = useState(false);
    const [timeUntilExpiration, setTimeUntilExpiration] = useState(null);
    
    useEffect(() => {
        const checkTokenExpiration = () => {
            const token = localStorage.getItem('accessToken');
            if (!token) {
                setIsExpired(true);
                return;
            }
            
            try {
                const payload = jwt.decode(token);
                const currentTime = Math.floor(Date.now() / 1000);
                const timeLeft = payload.exp - currentTime;
                
                if (timeLeft <= 0) {
                    setIsExpired(true);
                    return;
                }
                
                setTimeUntilExpiration(timeLeft);
                setIsExpired(false);
                
                // ✅ Refresh token 5 minutes before expiration
                if (timeLeft <= 300) {
                    refreshToken();
                }
            } catch (error) {
                setIsExpired(true);
            }
        };
        
        checkTokenExpiration();
        
        // ✅ Check every minute
        const interval = setInterval(checkTokenExpiration, 60000);
        
        return () => clearInterval(interval);
    }, []);
    
    return { isExpired, timeUntilExpiration };
}

Solution 4: Backend Token Validation with Expiration Check

// ✅ Express.js middleware with expiration handling
const jwt = require('jsonwebtoken');

function 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, process.env.JWT_SECRET, (err, decoded) => {
        if (err) {
            if (err.name === 'TokenExpiredError') {
                return res.status(401).json({ 
                    error: 'Token expired',
                    expired: true 
                });
            }
            return res.status(403).json({ error: 'Invalid token' });
        }
        
        req.user = decoded;
        next();
    });
}

// ✅ Token refresh endpoint
app.post('/api/refresh', async (req, res) => {
    const { refreshToken } = req.body;
    
    if (!refreshToken) {
        return res.status(401).json({ error: 'Refresh token required' });
    }
    
    try {
        // ✅ Verify refresh token
        const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
        const userId = decoded.userId;
        
        // ✅ Check if refresh token is still valid in database
        const storedRefreshToken = await getUserRefreshToken(userId, refreshToken);
        if (!storedRefreshToken) {
            return res.status(403).json({ error: 'Invalid refresh token' });
        }
        
        // ✅ Generate new tokens
        const newAccessToken = jwt.sign(
            { userId: decoded.userId },
            process.env.JWT_SECRET,
            { expiresIn: '15m' } // Short-lived access token
        );
        
        const newRefreshToken = jwt.sign(
            { userId: decoded.userId },
            process.env.REFRESH_TOKEN_SECRET,
            { expiresIn: '7d' } // Longer-lived refresh token
        );
        
        // ✅ Update refresh token in database
        await updateUserRefreshToken(userId, newRefreshToken);
        
        res.json({
            accessToken: newAccessToken,
            refreshToken: newRefreshToken
        });
    } catch (error) {
        return res.status(403).json({ error: 'Invalid refresh token' });
    }
});

Solution 5: Proactive Token Refresh

// ✅ Proactive token refresh before expiration
class ProactiveTokenManager {
    constructor() {
        this.refreshTimer = null;
    }
    
    scheduleTokenRefresh(token) {
        // ✅ Clear existing timer
        if (this.refreshTimer) {
            clearTimeout(this.refreshTimer);
        }
        
        try {
            const payload = jwt.decode(token);
            const currentTime = Math.floor(Date.now() / 1000);
            const timeUntilExpiration = payload.exp - currentTime;
            
            // ✅ Refresh 2 minutes before expiration
            const refreshTime = Math.max(0, (timeUntilExpiration - 120) * 1000);
            
            this.refreshTimer = setTimeout(async () => {
                try {
                    await this.refreshToken();
                } catch (error) {
                    console.error('Token refresh failed:', error);
                    // ✅ Handle refresh failure
                    this.handleRefreshFailure();
                }
            }, refreshTime);
        } catch (error) {
            console.error('Failed to schedule token refresh:', error);
        }
    }
    
    async refreshToken() {
        const refreshToken = localStorage.getItem('refreshToken');
        if (!refreshToken) {
            throw new Error('No refresh token available');
        }
        
        const response = await fetch('/api/refresh', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ refreshToken })
        });
        
        if (!response.ok) {
            throw new Error('Token refresh failed');
        }
        
        const data = await response.json();
        localStorage.setItem('accessToken', data.accessToken);
        
        // ✅ Schedule next refresh
        this.scheduleTokenRefresh(data.accessToken);
        
        return data.accessToken;
    }
    
    handleRefreshFailure() {
        // ✅ Clear tokens and redirect to login
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
        window.location.href = '/login';
    }
}

Solution 6: Token Expiration Monitoring

// ✅ Token expiration monitoring service
class TokenExpirationMonitor {
    constructor() {
        this.checkInterval = null;
        this.warningCallback = null;
        this.expiredCallback = null;
    }
    
    startMonitoring(warningMinutes = 5) {
        this.stopMonitoring();
        
        this.checkInterval = setInterval(() => {
            const token = localStorage.getItem('accessToken');
            if (!token) return;
            
            try {
                const payload = jwt.decode(token);
                const currentTime = Math.floor(Date.now() / 1000);
                const timeLeft = payload.exp - currentTime;
                
                if (timeLeft <= 0) {
                    // ✅ Token expired
                    if (this.expiredCallback) {
                        this.expiredCallback();
                    }
                    this.stopMonitoring();
                    return;
                }
                
                // ✅ Warning threshold
                if (timeLeft <= warningMinutes * 60 && this.warningCallback) {
                    this.warningCallback(timeLeft);
                }
            } catch (error) {
                console.error('Token monitoring error:', error);
            }
        }, 30000); // Check every 30 seconds
    }
    
    stopMonitoring() {
        if (this.checkInterval) {
            clearInterval(this.checkInterval);
            this.checkInterval = null;
        }
    }
    
    setCallbacks(onWarning, onExpired) {
        this.warningCallback = onWarning;
        this.expiredCallback = onExpired;
    }
}

// ✅ Usage
const monitor = new TokenExpirationMonitor();
monitor.setCallbacks(
    (secondsLeft) => {
        console.warn(`Token expires in ${Math.floor(secondsLeft / 60)} minutes`);
        // Show warning to user
    },
    () => {
        alert('Your session has expired. Please log in again.');
        // Redirect to login
        window.location.href = '/login';
    }
);
monitor.startMonitoring(5); // Warn 5 minutes before expiration

Solution 7: Graceful Token Expiration Handling

// ✅ Graceful handling of expired tokens
function handleExpiredToken(error) {
    if (error.response?.status === 401 && 
        (error.response.data.error === 'Token expired' || 
         error.response.data.message?.includes('expired'))) {
        
        // ✅ Show user-friendly message
        showNotification('Your session has expired. Please log in again.', 'warning');
        
        // ✅ Clear local storage
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
        
        // ✅ Redirect after delay to show notification
        setTimeout(() => {
            window.location.href = '/login';
        }, 2000);
        
        return true; // Handled
    }
    
    return false; // Not handled
}

// ✅ Notification utility
function showNotification(message, type = 'info') {
    const notification = document.createElement('div');
    notification.className = `notification notification-${type}`;
    notification.textContent = message;
    document.body.appendChild(notification);
    
    setTimeout(() => {
        notification.remove();
    }, 5000);
}

Solution 8: Token Rotation Strategy

// ✅ Implement token rotation for security
class SecureTokenManager {
    constructor() {
        this.currentToken = null;
        this.rotationThreshold = 300; // 5 minutes
    }
    
    async rotateTokenIfNeeded() {
        const token = localStorage.getItem('accessToken');
        if (!token) return;
        
        const payload = jwt.decode(token);
        const currentTime = Math.floor(Date.now() / 1000);
        const timeLeft = payload.exp - currentTime;
        
        // ✅ Rotate token if approaching threshold
        if (timeLeft <= this.rotationThreshold) {
            try {
                const newToken = await this.refreshToken();
                this.currentToken = newToken;
                return newToken;
            } catch (error) {
                console.error('Token rotation failed:', error);
                throw error;
            }
        }
        
        this.currentToken = token;
        return token;
    }
    
    async getValidToken() {
        if (this.currentToken) {
            const payload = jwt.decode(this.currentToken);
            const currentTime = Math.floor(Date.now() / 1000);
            
            if (payload.exp > currentTime) {
                return this.currentToken;
            }
        }
        
        return await this.rotateTokenIfNeeded();
    }
}

Solution 9: Session Management with Tokens

// ✅ Comprehensive session management
class SessionManager {
    constructor() {
        this.tokenManager = new TokenManager();
        this.sessionTimeout = null;
        this.inactivityTimeout = null;
        this.maxInactivityTime = 30 * 60 * 1000; // 30 minutes
    }
    
    startSession() {
        this.resetInactivityTimer();
        this.setupEventListeners();
    }
    
    resetInactivityTimer() {
        if (this.inactivityTimeout) {
            clearTimeout(this.inactivityTimeout);
        }
        
        this.inactivityTimeout = setTimeout(() => {
            this.endSession('Inactivity timeout');
        }, this.maxInactivityTime);
    }
    
    setupEventListeners() {
        ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'].forEach(event => {
            document.addEventListener(event, () => this.resetInactivityTimer(), true);
        });
    }
    
    async endSession(reason = 'User logout') {
        if (this.inactivityTimeout) {
            clearTimeout(this.inactivityTimeout);
        }
        
        // ✅ Clear tokens
        this.tokenManager.clearTokens();
        
        // ✅ Notify backend
        try {
            await fetch('/api/logout', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ reason })
            });
        } catch (error) {
            console.error('Logout notification failed:', error);
        }
        
        // ✅ Redirect to login
        window.location.href = '/login';
    }
}

// ✅ Initialize session management
const sessionManager = new SessionManager();
sessionManager.startSession();

Solution 10: Testing Token Expiration

// ✅ Test token expiration handling
function testTokenExpiration() {
    // ✅ Create an expired token for testing
    const expiredPayload = {
        userId: 123,
        exp: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
        iat: Math.floor(Date.now() / 1000) - 7200  // 2 hours ago
    };
    
    const expiredToken = jwt.sign(expiredPayload, process.env.JWT_SECRET);
    
    // ✅ Test expiration detection
    const isExpired = jwt.decode(expiredToken).exp < Math.floor(Date.now() / 1000);
    console.assert(isExpired, 'Token should be detected as expired');
    
    // ✅ Test refresh mechanism
    try {
        const newToken = await refreshToken();
        console.log('Token refresh successful:', newToken);
    } catch (error) {
        console.error('Token refresh failed:', error);
    }
}

// ✅ Automated testing
describe('Token Expiration', () => {
    test('should detect expired tokens', () => {
        const expiredToken = createExpiredToken();
        expect(TokenManager.isTokenExpired(expiredToken)).toBe(true);
    });
    
    test('should refresh expired tokens', async () => {
        const result = await TokenManager.refreshToken();
        expect(result).toBeDefined();
        expect(jwt.decode(result).exp).toBeGreaterThan(Math.floor(Date.now() / 1000));
    });
});

Prevention Tips

  1. Short-lived tokens: Use shorter expiration times for access tokens
  2. Refresh tokens: Implement secure refresh token mechanisms
  3. Proactive refresh: Refresh tokens before they expire
  4. Monitoring: Monitor token expiration times
  5. Graceful handling: Handle expiration gracefully without disrupting user experience
  6. Security: Implement token rotation and proper cleanup
  7. Testing: Test expiration scenarios thoroughly

When to Contact Support

Contact your authentication service provider when:

  • Following all best practices still results in unexpected expiration
  • Suspected service-side token generation issues
  • Clock synchronization problems between systems
  • Unexpected changes in token expiration policies
  • Refresh token mechanisms are not working as expected
Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

Tutorials

Fix: JWT malformed error

Complete guide to fix 'JWT malformed' error. Learn how to properly handle, decode, and validate JSON Web Tokens in your applications.

January 8, 2026
Tutorials

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.

January 8, 2026
Tutorials

Fix: invalid_client OAuth error

Complete guide to fix 'invalid_client' OAuth error. Learn how to resolve client credentials and configuration issues in OAuth implementations.

January 8, 2026