JWT Token Expired Error — Causes, Detection, and Fix

💡A JWT token expired error means the token's exp claim is in the past. JWTs have a built-in expiry timestamp in the payload. When a request reaches the server with an expired token, authentication fails with 401. Fix: implement a refresh token flow to get a new access token before the old one expires. Decode the JWT to inspect its expiry with the JWT Decoder.

You may also see this as:

  • JsonWebTokenError: jwt expired
  • TokenExpiredError: jwt expired
  • 401 Unauthorized: Token has expired
  • JWT verification failed: token is expired
  • exp claim is in the past

Quick Diagnosis

If you seeTokenExpiredError: jwt expired” → it means the token's exp timestamp is before the current time do this: check the exp value in the JWT payload — use a JWT decoder to read it

If you see401 on all requests after some time” → it means access token has expired and no refresh logic is in place do this: implement refresh token flow — request new access token using refresh token

If you seetoken valid locally but expired on server” → it means server and client clocks are out of sync do this: check server time — JWT exp uses Unix timestamp, clock drift causes false expiry

If you seetoken expired immediately after login” → it means exp is set too short or there's a clock mismatch do this: decode the token — check iat (issued at) and exp difference

Quick Fix — Step by Step

  1. Decode the JWT and check the exp field (Unix timestamp)
  2. Compare exp with current time: exp * 1000 vs Date.now()
  3. Implement refresh token logic — request new access token before expiry
  4. Add an interceptor to catch 401 responses and refresh automatically
  5. Increase token TTL if expiry is too short for your use case

Inspect Your Token

Common Causes and Fixes

Read exp from JWT payload

❌ Wrong

const isExpired = token.exp < Date.now();

✅ Fixed

const payload = JSON.parse(atob(token.split('.')[1]));
const isExpired = payload.exp * 1000 < Date.now();

JWT exp is in seconds, Date.now() is milliseconds. Multiply exp by 1000 to compare.

No refresh handling

❌ Wrong

if (error.status === 401) {
  logout();
}

✅ Fixed

if (error.status === 401 && !error.config._retry) {
  error.config._retry = true;
  const newToken = await refreshAccessToken();
  return retryWithNewToken(error.config, newToken);
}

Logging out on every 401 is bad UX. Try refreshing first, then logout if refresh fails.

Wrong exp comparison

❌ Wrong

if (new Date(payload.exp) < new Date()) { /* expired */ }

✅ Fixed

if (payload.exp * 1000 < Date.now()) { /* expired */ }

new Date(payload.exp) treats exp as milliseconds. JWT exp is Unix seconds — multiply by 1000.

Verify with expiry handling in Node

❌ Wrong

const payload = jwt.decode(token); // doesn't verify!

✅ Fixed

try {
  const payload = jwt.verify(token, process.env.JWT_SECRET);
} catch(e) {
  if (e instanceof jwt.TokenExpiredError) {
    // handle specifically
  }
}

jwt.decode() skips verification. Always use jwt.verify() on the server.

Real-World Context

Check JWT expiry before request

function isTokenExpired(token) {
  const payload = JSON.parse(atob(token.split('.')[1]));
  return payload.exp * 1000 < Date.now();
}

Proactively check before each request to avoid sending expired tokens.

Axios interceptor for auto-refresh

axios.interceptors.response.use(
  r => r,
  async error => {
    if (error.response?.status === 401) {
      const newToken = await refreshAccessToken();
      error.config.headers.Authorization = `Bearer ${newToken}`;
      return axios(error.config);
    }
    return Promise.reject(error);
  }
);

Intercept 401 responses, refresh the token, and retry the original request transparently.

Proactive token refresh

const REFRESH_BUFFER = 5 * 60 * 1000; // 5 min
const payload = JSON.parse(atob(token.split('.')[1]));
const expiresAt = payload.exp * 1000;
if (expiresAt - Date.now() < REFRESH_BUFFER) {
  await refreshToken();
}

Refresh 5 minutes before expiry to avoid requests failing mid-flight.

Related Guides

Frequently Asked Questions

How long should a JWT token last?

Access tokens: 15 minutes to 1 hour. Refresh tokens: 7-30 days. Short-lived access tokens reduce risk if a token is leaked.

Can I extend a JWT token's expiry?

No. JWTs are immutable — the exp is signed into the token. To extend, issue a new token with a new expiry using a refresh token.

How do I invalidate a JWT before it expires?

JWTs are stateless — you can't invalidate them without a blocklist. Store invalidated token IDs in a fast store like Redis and check on each request.

All tools run in your browser. Your data never leaves your device.