JWT Decoder

Decode Expired JWT Token: Check Token Expiry

Decode a JWT with an expired exp claim. Inspect the payload to see when it expired and troubleshoot authentication issues.

100% client-side. Your data never leaves your browser.

Related Tools

Decode Expired JWT Token

Decoding an expired JWT reveals the same payload as a valid one. The token structure is unchanged; the exp claim just contains a timestamp in the past. This is useful for debugging authentication errors, checking what claims were issued, and figuring out when a token expired without having access to server logs.

The Decoded Token

The token in this example decodes to:

{
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  "payload": {
    "sub": "1234567890",
    "name": "Alice",
    "iat": 1600000000,
    "exp": 1600003600,
    "iss": "auth.example.com",
    "aud": "api.example.com"
  }
}

The iat is 1600000000 (September 13, 2020, 12:26:40 UTC). The exp is 1600003600, exactly one hour later. Both are Unix timestamps in seconds. This token expired in September 2020.

Time Claims and What They Mean

iat (issued at)

The Unix timestamp when the token was issued. Used to detect tokens that were issued before a key rotation or before a user’s password was changed. Some systems reject tokens with an iat before the user’s last password change, even if the token has not technically expired.

exp (expires at)

The Unix timestamp after which the token must be rejected. Servers check current_time > exp and return 401 if true. The check is strict: a token where exp == current_time is already expired by most implementations.

nbf (not before)

Less common. The Unix timestamp before which the token must not be accepted. Useful when you issue a token intended to be used in the future, or when you want a grace period after issuance before the token becomes active.

Relationship between the three

issued at (iat) ──► valid from (nbf) ──► valid until (exp)

In practice, iat and nbf are often the same value or nbf is omitted. The critical pair for most implementations is iat and exp.

Token Refresh Flow

When an access token expires, the client should:

  1. Detect the 401 response (or check expiry client side before the request)
  2. Send the refresh token to the token endpoint (POST /oauth/token or similar)
  3. Receive a new access token (and sometimes a new refresh token)
  4. Retry the original request with the new access token
  5. If the refresh token is also expired, redirect the user to the login flow

A well implemented client library handles steps 1 through 4 transparently. Users never see expiry errors unless their refresh token has also expired.

async function fetchWithRefresh(url, options) {
  let response = await fetch(url, {
    ...options,
    headers: { Authorization: `Bearer ${getAccessToken()}` }
  });

  if (response.status === 401) {
    await refreshAccessToken();
    response = await fetch(url, {
      ...options,
      headers: { Authorization: `Bearer ${getAccessToken()}` }
    });
  }

  return response;
}

Clock Skew

JWT expiry compares the exp claim against the server’s current clock. If the server that issued the token and the server validating it have clocks that differ by more than a few seconds, valid tokens can be rejected immediately.

The standard tolerance is a 5 minute leeway (exp + 300 > current_time). Most JWT libraries support a configurable clock skew tolerance. Production systems should run NTP or a similar time synchronization protocol to keep server clocks within a few seconds of each other.

Common Debugging Scenarios

Token expired between issuance and first use

The client received a token but did not use it immediately. This happens in batch jobs, queued tasks, or clients with aggressive caching. If your access token lifetime is 1 hour and a background job sits in a queue for 90 minutes before running, its token will be expired on first use. Solutions: fetch a fresh token immediately before the request, or use a service account with tokens that have a longer lifetime for background jobs.

Server clock drift

The issuing server’s clock is ahead of the validating server’s clock. A token issued at time T with a 5 minute expiry is valid until T+5m by the issuer’s clock. If the validator’s clock is 10 minutes behind, it sees the token as valid until T+5m+10m. If the validator’s clock is 10 minutes ahead, it rejects the token immediately after issuance. Monitor clock drift on all servers that issue or validate JWTs.

Timezone confusion

The exp and iat claims are always Unix timestamps in UTC seconds. They are not formatted date strings. Confusion happens when developers log these values and interpret them as local time, or when they construct JWTs manually using local time instead of UTC epoch. To convert an exp value to a readable time: new Date(exp * 1000).toISOString() in JavaScript, or datetime.fromtimestamp(exp, tz=timezone.utc) in Python.

Debugging with the token decoder

Paste any JWT, even an expired one, to see the exact exp value. Use the Unix timestamp converter to convert it to a human readable date. Compare it to the iat to see the token’s intended lifetime and to the current time to see how long ago it expired.