JWT Expiration Explained

How token lifetime works — exp, iat, nbf claims and best practices for secure token expiration

1. The Three Time-Based Claims

JWT defines three registered claims that control when a token is valid. All three use Unix timestamps — the number of seconds since January 1, 1970 00:00:00 UTC.

{
  "sub": "user_12345",
  "iat": 1735686000,    // Issued: Jan 1, 2025 10:00:00 UTC
  "nbf": 1735686000,    // Valid from: Jan 1, 2025 10:00:00 UTC
  "exp": 1735689600     // Expires: Jan 1, 2025 11:00:00 UTC
}
iat

When it was created

nbf

When it becomes valid

exp

When it stops being valid

2. exp — Expiration Time

The exp claim is the most important time-based claim. It sets a hard deadline after which the token must be rejected. Without exp, a compromised token could be used indefinitely.

// Token valid for 1 hour from now
{
  "exp": 1735689600  // Unix timestamp in seconds
}

// JavaScript: Set expiration to 1 hour from now
const exp = Math.floor(Date.now() / 1000) + 3600;

Common mistake: Using milliseconds instead of seconds. JavaScript's Date.now() returns milliseconds, but JWT exp uses seconds. Always divide by 1000:Math.floor(Date.now() / 1000) + ttlInSeconds

Validation Logic

When a server receives a JWT, it checks:

const now = Math.floor(Date.now() / 1000);

if (payload.exp && now >= payload.exp) {
  throw new Error("Token has expired");
}
// Token is still valid — proceed

3. iat — Issued At

The iat claim records when the token was created. It's not used for expiration directly, but serves several purposes:

  • Age checking: Reject tokens older than a certain threshold, even if exp hasn't passed
  • Revocation: Invalidate all tokens issued before a specific time (e.g., after a password change)
  • Auditing: Know exactly when each token was generated for debugging and logging
// Reject tokens issued before user changed password
const passwordChangedAt = user.passwordChangedAt;
if (payload.iat && payload.iat < passwordChangedAt) {
  throw new Error("Token issued before password change");
}

4. nbf — Not Before

The nbf claim sets the earliest time a token can be used. Tokens presented before this time must be rejected. This is useful for:

  • Scheduled access: Grant access starting at a future date
  • Pre-issued tokens: Generate tokens in advance that activate later
  • Time-limited promotions: Tokens that only work during a specific window
// Token valid from tomorrow at midnight to 7 days later
{
  "nbf": 1735776000,    // Tomorrow 00:00 UTC
  "exp": 1736380800,    // 7 days after nbf
  "sub": "user_12345",
  "access": "premium_content"
}

6. Access Tokens vs Refresh Tokens

The standard pattern for balancing security and user experience is the access + refresh token model:

The Flow

  1. User logs in → server issues an access token (15 min) and a refresh token (30 days)
  2. Client uses the access token for API requests
  3. Access token expires → client sends the refresh token to get a new access token
  4. Server validates the refresh token and issues a new access token
  5. Refresh token expires → user must log in again
// Access token — short-lived, sent with every request
{
  "sub": "user_12345",
  "role": "editor",
  "type": "access",
  "iat": 1735686000,
  "exp": 1735686900    // 15 minutes
}

// Refresh token — long-lived, stored securely
{
  "sub": "user_12345",
  "type": "refresh",
  "jti": "refresh_abc123",
  "iat": 1735686000,
  "exp": 1738278000    // 30 days
}

For a deeper dive into these patterns, see our authentication patterns guide.

7. Handling Clock Skew

In distributed systems, server clocks may not be perfectly synchronized. A token issued by Server A might appear "from the future" to Server B if their clocks differ by a few seconds.

// Allow 30 seconds of clock skew
const CLOCK_SKEW_SECONDS = 30;
const now = Math.floor(Date.now() / 1000);

// Check expiration with tolerance
if (payload.exp && now >= payload.exp + CLOCK_SKEW_SECONDS) {
  throw new Error("Token has expired");
}

// Check nbf with tolerance
if (payload.nbf && now < payload.nbf - CLOCK_SKEW_SECONDS) {
  throw new Error("Token is not yet valid");
}

Recommended tolerance: 30-60 seconds is standard. More than 5 minutes suggests a clock synchronization problem that should be fixed at the infrastructure level (use NTP).

8. What to Do When a Token Expires

When a JWT expires, the server should return a 401 Unauthorized response. The client then decides how to handle it:

Option 1: Refresh the Token

If you have a refresh token, use it to silently get a new access token without interrupting the user.

Option 2: Re-authenticate

If no refresh token is available or it's also expired, redirect the user to the login page.

Option 3: Proactive Refresh

Check the token's exp before each request. If it expires within the next few minutes, refresh proactively to avoid failed requests.

// Proactive refresh — check before each API call
function isTokenExpiringSoon(token, thresholdSeconds = 300) {
  const payload = JSON.parse(atob(token.split('.')[1]));
  const now = Math.floor(Date.now() / 1000);
  return payload.exp - now < thresholdSeconds;
}

// Usage
if (isTokenExpiringSoon(accessToken)) {
  accessToken = await refreshAccessToken(refreshToken);
}
await callApi(accessToken);

9. How to Check JWT Expiration

You can check whether a JWT has expired without verifying its signature — just decode the payload and compare the exp claim to the current time.

JavaScript

function isJwtExpired(token) {
  const payload = JSON.parse(atob(token.split('.')[1]));
  if (!payload.exp) return false; // No expiration set
  return Date.now() >= payload.exp * 1000;
}

Python

import base64, json, time

def is_jwt_expired(token):
    payload = token.split('.')[1]
    payload += '=' * (4 - len(payload) % 4)
    decoded = json.loads(base64.urlsafe_b64decode(payload))
    if 'exp' not in decoded:
        return False
    return time.time() >= decoded['exp']

Using Our Tool

The fastest way: paste your token into our JWT decoder. The decoded payload shows the exp timestamp, which you can compare to the current time.

Check Your Token Expiration

Paste a JWT to instantly see its expiration time, issued-at timestamp, and whether it's still valid.