The claims involved Three registered claims control a JWT's validity window:
exp(expiration time) — a Unix timestamp after which the token must be rejectednbf(not before) — the token isn't valid until this timeiat(issued at) — when the token was created, useful for auditing and computing age
All three are Unix timestamps in seconds, not milliseconds — a classic off-by-1000x bug.
Why expiration matters A JWT can't be revoked the way a session can (there's no server-side record to delete, by design). Expiration is the primary mechanism limiting how long a leaked or stolen token remains useful. Short expirations (5–15 minutes for access tokens) shrink the blast radius of a leak dramatically.
What clock skew actually is Clock skew is the small, usually unavoidable difference between the system clock on the machine that issued a token and the clock on the machine verifying it. Even NTP-synced servers can drift by a few seconds. If your verifier does an exact, zero-tolerance comparison against `exp` or `nbf`, tokens can be rejected moments before they should logically expire, or accepted moments after — or worse, a token can appear to arrive "before it was issued" if `nbf` is checked too strictly right after issuance.
The standard fix: clock tolerance Most JWT libraries (`jsonwebtoken` in Node, `PyJWT` in Python, `jose` libraries generally) support a **clockTolerance** or **leeway** option, typically set to somewhere between 5 and 60 seconds. This adds a small grace window to `exp` and `nbf` comparisons without meaningfully weakening security.
- In
jsonwebtoken: pass{ clockTolerance: 30 }tojwt.verify. - In
PyJWT: passleeway=30tojwt.decode.