Decode the claims locally with the JWT Decoder.
Why tokens expire at all
Short-lived access tokens limit the damage of a leaked token: an attacker has only minutes, not months, to use it. The exp claim records the expiration as an absolute Unix timestamp in seconds.
Most authentication libraries check exp automatically. When they reject a token you expected to be valid, the cause is usually one of three things: an actually expired token, a unit mistake, or clock skew.
The claims that control lifetime
All three are NumericDate: seconds since the Unix epoch (1970-01-01T00:00:00Z). Converted to UTC, a value like 1784563200 becomes a plain date and time — usually where the bug becomes obvious. Check each claim separately.
| Claim | Meaning | Common pitfall |
|---|---|---|
| exp | Expiration time (Unix seconds) | Comparing against milliseconds instead of seconds |
| iat | Issued-at time (Unix seconds) | A far-future iat indicates skew or a forged token |
| nbf | Not valid before (Unix seconds) | Token is rejected until this time; check for future values |
| iss / aud / sub | Issuer, audience, subject | Mismatch looks like an expiry bug but is a config issue |
What clock skew actually does
Clock skew is the difference between the clock of the token issuer and the clock of the verifier. NTP keeps most servers close, but containers, CI runners, and cloud functions can drift or start with a frozen clock.
If the verifier is behind the issuer, a freshly issued token can look not-yet-valid. If the verifier is ahead, a valid token looks expired. Both produce confusing errors like "token used before issued" or "token expired".
Verification libraries bridge the gap with leeway, a small grace period around the claims. With 30 seconds of leeway, an exp up to 30 seconds in the past still passes, and an nbf up to 30 seconds in the future is tolerated. That absorbs ordinary NTP drift without touching the token.
Debug expiry errors locally, step by step
- Copy the failing token and open the JWT Decoder.
- Read exp, iat, and nbf from the payload.
- Convert them to human-readable times with the Timestamp Converter.
- Compare each value with the current UTC time on the server.
- Check the server clock itself with the date command and compare it to an NTP source.
- Add a small leeway (30–60 seconds) to verification and re-test.
Warning: Do not fix skew by setting leeway to hours. Large leeway silently extends token lifetime and weakens the security that exp is supposed to provide.
Prevention checklist
- Keep server clocks synced with NTP, including CI runners and containers
- Verify with a leeway of 30–60 seconds, never more
- Always compare exp/iat/nbf in Unix seconds, never milliseconds
- Mint longer-lived tokens in test environments instead of raising leeway in production
- Do not mint a new token on every request; reuse the current one until exp unless there is a real rotation reason
FAQ
Q.Why does a token expire before its stated lifetime?
A.Usually because the verifier clock is ahead of the issuer clock (clock skew) or because exp was computed with the wrong unit. Decode the token and compare the claim against current UTC time. If exp is only a minute or two in the past on a fresh token, that is skew; off by a factor of 1000, that is milliseconds.
Q.What leeway should I use?
A.30–60 seconds is the common range. Enough to absorb NTP drift, small enough to keep expiry meaningful. Larger leeway for CI convenience should be implemented by issuing longer-lived tokens, not by loosening verification. A looser check applies everywhere, not just CI.
Q.Should I use seconds or milliseconds for JWT time claims?
A.Seconds. RFC 7519 defines NumericDate as seconds since the Unix epoch. Feeding milliseconds into exp comparisons is one of the most common causes of phantom expiration bugs. A fast tell: a fresh token whose exp lands in the 2030s, or whose lifetime works out to centuries.
Q.Why does my library report 'token used before issued'?
A.That error comes from the iat or nbf claim: the verifier thinks the token was minted in the future. Usually the verifier clock runs behind the issuer clock, or the issuer clock was fast when the token was created. A small leeway absorbs it; a gap of minutes means one clock needs an NTP sync.
References
- RFC 7519 – JSON Web Token (JWT): https://www.rfc-editor.org/rfc/rfc7519
- RFC 7515 – JSON Web Signature (JWS): https://www.rfc-editor.org/rfc/rfc7515
- OWASP JSON Web Token Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html
Decode a token now
Header and payload in your browser, read-only. Check exp, iat, and nbf in seconds.
Read the claims before changing code
Decode the failing token, convert exp, iat, and nbf with the Timestamp Converter, and compare against the server clock in UTC. Most 'expired' errors are skew or unit mistakes.
If clocks are in sync and the math checks out, a leeway of 30-60 seconds absorbs NTP drift without gutting expiry. Decode tokens with the JWT Decoder.