flashman
← All posts

JWT expired token errors: debug clock skew and exp claims

Debug JWT expired and not-before errors by checking clock skew, Unix seconds, issuer timezones, and decoded claims locally before changing auth code safely.

2026-06-22 · 6 min read · Rahul Chitturi

  • jwt
  • auth
  • debugging

A JWT that looks valid can still fail with token expired, token not active, or invalid issued-at errors. These bugs often appear after a deploy, a region failover, or a move from local development to a hosted identity provider.

Before changing auth middleware, inspect the time-related claims and the clocks involved. Most fixes come from understanding exp, nbf, iat, Unix seconds, milliseconds, and the small amount of clock skew your verifier allows.

Read the time claims first

JWT time claims are numeric dates measured in seconds since the Unix epoch. JavaScript timestamps are usually milliseconds, so multiplying or dividing at the wrong boundary creates tokens that appear expired decades ago or valid far into the future.

  • exp: the token must be rejected after this time
  • nbf: the token must not be accepted before this time
  • iat: the token issue time, useful for freshness checks
  • auth_time: when the user authenticated, common in OpenID Connect

Check every clock in the path

A five-minute difference between systems can break login even when the token issuer and verifier are both configured correctly. Containers, serverless regions, local laptops, and test fixtures may not agree on time during incidents.

Compare the issuer time, API server time, browser-visible time, and any gateway or edge middleware that validates tokens before your app sees the request.

  • Confirm hosts use NTP or the cloud provider time service
  • Check whether staging and production have different skew tolerances
  • Look for cached tokens reused after a deployment or password reset
  • Verify test fixtures are generated at runtime instead of hard-coded forever

Separate timezone bugs from epoch bugs

JWT numeric dates are timezone-free UTC instants. If a dashboard, support ticket, or log line converts them to local time, record the offset before comparing. A display timezone mismatch is confusing, but it is different from a token generated with milliseconds in the exp field.

When the raw claim has 10 digits, it is probably seconds. When it has 13 digits, it is probably milliseconds and does not belong directly in a JWT numeric date.

Use skew carefully

Clock skew is a small tolerance that absorbs real-world drift. It should not become a hidden extension of session lifetime. If tokens regularly need ten or fifteen minutes of skew to pass, fix the clocks or token generation logic instead.

  • Keep skew short and documented in auth configuration
  • Apply the same policy across API services that trust the issuer
  • Do not ignore exp in order to reduce login errors
  • Log claim values in non-production with secrets redacted

A Flashman workflow

Decode a non-production JWT locally, convert exp and nbf with a timestamp converter, format any identity provider JSON error, then diff working and failing decoded claims. This narrows the problem before you edit verifier code.

Flashman runs these checks in the browser, which is useful when auth data includes internal tenants, audiences, or callback hosts. Prefer synthetic tokens for examples and avoid pasting production bearer tokens into tickets.

Try these tools