DevFlow logoDevFlow
TokenExpiredErrorVerified Production Fix

TokenExpiredError: jwt expired

Debug and fix "jwt expired" TokenExpiredError in Node.js, Express, and Next.js. Inspect exp claims, configure clock tolerance, and implement refresh tokens.

Root Cause Mechanical Summary

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.

Vulnerable / Problematic Syntax
Before
// Fails immediately if clock drifts or token expired
jwt.verify(token, process.env.JWT_SECRET!);
Production-Safe Remediation
After
// 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.

Step-by-Step Triage Checklist

  • 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.

Refresh Token Middleware Handler

middleware/auth.ts

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 });
  }
}

Quick CLI Fix / Diagnosis

devflow run jwt-decoder -i "$TOKEN"

Frequently Asked Questions

What is the recommended lifetime for JWT access tokens?
Industry standard security practice recommends 15 to 60 minutes for access tokens, paired with a longer-lived HTTP-only refresh token (7 to 30 days).
Was this guide / tool helpful to you?