JWT exp Claim (Expiration)
The JWT exp (expiration) claim is a Unix timestamp after which the token must be rejected. Learn exp validation, the milliseconds-vs-seconds trap, clock skew leeway, and recommended token lifetimes.
The `exp` (expiration time) claim is a Unix timestamp (seconds since the Unix epoch, January 1 1970 UTC) after which the token must not be accepted. It is defined in RFC 7519 §4.1.4 and is the most security-critical registered claim. A token without `exp` is valid until the signing key rotates: a stolen token with no expiration is a permanent credential.
Format and values
A numeric Unix timestamp in **seconds** (not milliseconds). Example: `"exp": 1750090800`. JavaScript's `Date.now()` returns milliseconds; a missing division by 1000 is the most common JWT bug in production.
Validation rule
Reject any token whose `exp` is in the past. Apply a clock-skew leeway of 30–60 seconds to handle distributed-system clock differences. Most JWT libraries check `exp` automatically, but you must confirm your library rejects expired tokens by default rather than optionally. Apply leeway only to `exp` and `nbf`, never to `iat`.
Common mistakes
- Setting exp in milliseconds instead of seconds. A token with exp set in milliseconds has a value around 1,750,000,000,000: roughly the year 57,000 CE in Unix seconds. The token appears valid indefinitely. Always use Math.floor(Date.now() / 1000).
- Issuing tokens without exp. A token without exp is valid forever; a single leak becomes a permanent credential.
- Disabling exp validation to debug. Re-enable it before shipping; an expired-token check disabled in development that reaches production is a common incident.
Code examples
Set exp correctly (Node.js)
// Wrong: milliseconds, ~year 57,000 CE
const exp = Date.now() + 15 * 60 * 1000;
// Correct: seconds
const exp = Math.floor(Date.now() / 1000) + 15 * 60;
// Or let the library do it
const token = jwt.sign({ sub: "user_123" }, secret, { expiresIn: "15m" }); Verify exp with clock skew (Python: PyJWT)
jwt.decode(
token,
secret,
algorithms=["HS256"],
leeway=60, # 60s skew tolerance for exp/nbf
) Frequently asked questions
-
What is the exp claim in a JWT?
exp (expiration time) is an RFC 7519 registered claim: a Unix timestamp in seconds after which the token must be rejected. It is the most security-critical registered claim: without exp, a stolen token is valid until the signing key rotates. Standard access token lifetimes are 15 minutes; high-security APIs use 5 minutes or less.
-
Is JWT exp in seconds or milliseconds?
Seconds. JWT timestamps (exp, nbf, iat) are Unix timestamps in seconds since the epoch. JavaScript's Date.now() returns milliseconds, so a missing division by 1000 is the most common JWT bug: the token appears valid until roughly the year 57,000 CE. Always use Math.floor(Date.now() / 1000) when computing exp manually, or let your library handle it (jwt.sign({ ... }, secret, { expiresIn: '15m' })).
-
How do I handle clock skew when validating JWT exp?
Apply a clock-skew leeway of 30 to 60 seconds. In jsonwebtoken use clockTolerance: 60; in PyJWT use leeway=60; in Spring Security the default is 60s. The leeway accounts for clock differences between the issuer and verifier. Apply leeway only to exp and nbf: never to iat, since a token claiming to be issued in the future is suspicious and should be rejected.