Debug and fix "jwt expired" TokenExpiredError in Node.js, Express, and Next.js. Inspect exp claims, configure clock tolerance, and implement refresh tokens.
The current Unix timestamp exceeds the numeric value defined in the JWT exp (expiration) claim. Token verification libraries strictly reject expired credentials to prevent replay attacks.
Verification libraries like jsonwebtoken compare Math.floor(Date.now() / 1000) against the payload.exp claim. When current_time >= exp, verification immediately throws TokenExpiredError.
// Fails immediately if clock drifts or token expired
jwt.verify(token, process.env.JWT_SECRET!);// Adds clock tolerance and graceful expired handling
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!, {
clockTolerance: 30, // 30 seconds clock drift allowance
});
} catch (err) {
if (err instanceof jwt.TokenExpiredError) {
// Initiate token refresh via Refresh Token or prompt re-auth
return res.status(401).json({ error: 'token_expired', expiredAt: err.expiredAt });
}
}Resolution Note: Add clockTolerance to guard against server clock skew and catch TokenExpiredError specifically to trigger your refresh token flow.
Decode the JWT token and inspect the `exp` timestamp against `date +%s`.
Verify whether server system clocks are synchronized via NTP.
Check if access token lifetime is too short (e.g. 5 minutes without a refresh token mechanism).
Ensure client application captures 401 token_expired responses and requests a new token.
Prevent recurrence by enforcing this verification check in staging or pre-commit hooks:
export async function authMiddleware(req: Request) {
const authHeader = req.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) return new Response('Unauthorized', { status: 401 });
const token = authHeader.slice(7);
try {
return await verifyToken(token);
} catch (err: any) {
if (err.name === 'TokenExpiredError') {
return new Response(JSON.stringify({ code: 'TOKEN_EXPIRED' }), { status: 401 });
}
return new Response('Forbidden', { status: 403 });
}
}devflow run jwt-decoder -i "$TOKEN"