JWT expiration is a built-in deadline that tells any server how long a token may be trusted. It lives in the token's exp (expiration time) claim, whose value is a NumericDate — the number of seconds since the Unix epoch (1970-01-01T00:00:00Z UTC), as defined by RFC 7519. When a server verifies a JWT, it rejects the token if the current time is at or past exp. Because the deadline is signed into the token itself, expiration works as an automatic, passive revocation: a stolen token stops working on its own, without the server keeping any list of invalid tokens. Most systems pair a short-lived access token (minutes) with a longer-lived refresh token (days) so users stay logged in while the credential actually sent on every request expires quickly.
That is the summary an AI overview would give you. The part it can't give you is the mental model of a token's lifetime — the three timestamps that bound it, why you can't un-issue a JWT once it's out, and how the numbers actually get computed. Here is the whole picture.
A JWT's lifetime: iat, nbf, exp on one timeline
Every JWT carries up to three time claims. iat marks when it was born, nbf marks the earliest moment it may be used, and exp marks the moment it dies. The window between nbf and exp is the only stretch during which a receiver will accept the token. The animation below follows the "current time" marker across that lifetime.
The three time-related claims
The JWT spec (RFC 7519, Section 4.1) defines exactly three registered time claims. All share one format — NumericDate, seconds (not milliseconds) since the Unix epoch — and all are optional, though exp is used almost universally.
| Claim | Name | Format | What it means |
|---|---|---|---|
exp | Expiration Time | NumericDate (seconds since Unix epoch) | The token MUST NOT be accepted at or after this time. This is the expiry. |
iat | Issued At | NumericDate (seconds since Unix epoch) | When the token was created. Used to measure the token's age (and, with exp, its total lifetime). |
nbf | Not Before | NumericDate (seconds since Unix epoch) | The token MUST NOT be accepted before this time. Useful for tokens that should activate later. |
A key subtlety: exp is checked as "current time is before exp" while nbf is checked as "current time is after or equal to nbf." Both comparisons run against the verifying server's clock, which is why clock skew and NTP synchronization (covered below) matter.
The Concept of JWT Expiration
JWT expiration is a security feature that limits how long a token can be used. The "exp" (expiration time) claim specifies when a token becomes invalid and should no longer be accepted by the server. Once a token expires, presenting it is no longer sufficient for authentication—the user must obtain a new token.
Expiration is critical because it limits the damage if a token is stolen or compromised. An attacker who captures a token gains access only until it expires. Without expiration, a compromised token could be used indefinitely, potentially forever. Expiration provides a natural revocation mechanism even without maintaining a database of invalid tokens.
Most authentication systems use short-lived access tokens (minutes to hours) to minimize the window of vulnerability, combined with longer-lived refresh tokens that can obtain new access tokens. This architecture balances security and user experience—users don't need to log in constantly, but compromised tokens have limited utility.
Want to inspect a real token's exp, iat, and nbf right now? Paste one into the decoder below — it runs entirely in your browser and never sends the token anywhere.
How JWT Expiration Works
The "exp" claim contains a numeric value representing seconds since the Unix epoch (January 1, 1970). For example, "exp":1735689600 represents January 1, 2025, at 00:00:00 UTC. When the server verifies a JWT, it checks whether the current time is before the expiration time. If the current time is past the expiration time, the token is invalid.
Verification frameworks handle expiration automatically. Most JWT libraries check the exp claim by default and reject expired tokens. In Node.js with jsonwebtoken:
const decoded = jwt.verify(token, secret);
// If token is expired, verify throws an error
The verification automatically rejects tokens where current_time > exp. You don't need to manually check expiration—the library does it.
Calculating expiration times is straightforward. To create a token that expires in one hour:
const payload = {
sub: 'user_123',
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour from now
};
const token = jwt.sign(payload, secret);
The calculation adds 3600 seconds (one hour) to the current Unix timestamp. This approach works across time zones since Unix timestamps are always UTC.
Choosing Appropriate Expiration Times
Selecting expiration times involves balancing security and user experience. Short expiration times improve security—a compromised token can be used for a shorter period. Long expiration times improve user experience—users don't need to re-authenticate as frequently.
For access tokens used in API requests, typical expiration ranges are 5 minutes to 1 hour. Many modern APIs use 15 minutes, providing a reasonable balance. Very short expiration (5 minutes) offers maximum security but may impact user experience with frequent interruptions.
For browser-based applications, shorter expiration times are generally safe since the entire token is transmitted with each request, making it easier to refresh automatically. A 15-minute access token is reasonable for web applications.
For refresh tokens, much longer expiration (days to weeks) is appropriate since they're used less frequently and stored securely. Refresh tokens might expire in 7 days or even 30 days, but they should still expire eventually. Indefinite refresh token validity creates security risks.
For service-to-service authentication, expiration might be shorter (5 minutes) since services can request new tokens automatically. For background jobs, expiration can be longer since the token is issued and used within a predictable timeframe.
The Refresh Token Pattern
The refresh token pattern elegantly solves the expiration dilemma. Instead of requiring long-lived access tokens, you issue short-lived access tokens and separate, longer-lived refresh tokens:
- User logs in and receives both access token (15 minutes) and refresh token (7 days)
- Client uses the access token for API requests
- When the access token expires, client sends the refresh token to get a new access token
- The refresh token can obtain multiple new access tokens before it expires
- When the refresh token expires, the user must log in again
This pattern allows users to remain logged in for days while access tokens expire after minutes. If an access token is compromised, it's useful only for 15 minutes. If a refresh token is compromised, it can be used for 7 days but only to obtain new access tokens.
Implementation in Node.js:
// Login endpoint
app.post('/login', (req, res) => {
const accessToken = jwt.sign(
{ sub: user.id },
secret,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ sub: user.id, type: 'refresh' },
refreshSecret,
{ expiresIn: '7d' }
);
res.json({ accessToken, refreshToken });
});
// Refresh endpoint
app.post('/refresh', (req, res) => {
const refreshToken = req.body.refreshToken;
try {
const decoded = jwt.verify(refreshToken, refreshSecret);
const newAccessToken = jwt.sign(
{ sub: decoded.sub },
secret,
{ expiresIn: '15m' }
);
res.json({ accessToken: newAccessToken });
} catch (err) {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
The refresh token and access token use different secrets, allowing them to be verified independently. The refresh token includes a type claim to distinguish it from access tokens.
Clock Skew and Time Synchronization
JWT expiration verification compares the exp claim to the current server time. If servers have significant time differences, legitimate tokens might appear expired on some servers. Distributed systems must maintain reasonable time synchronization.
Most organizations use NTP (Network Time Protocol) to synchronize time across servers. This keeps time differences within milliseconds. JWT libraries provide a clock skew tolerance to handle minor discrepancies:
jwt.verify(token, secret, { clockTolerance: 10 }); // 10 seconds tolerance
This allows tokens to remain valid for 10 seconds past their expiration time, accommodating minor time differences. Set clock tolerance to a reasonable value (5-30 seconds), not minutes.
Revocation Alongside Expiration
Expiration is a passive revocation mechanism—tokens automatically become invalid at a certain time. However, sometimes you need active revocation before expiration, such as when a user logs out or when suspicious activity is detected.
Several approaches complement expiration-based revocation. The simplest is token blacklisting: maintain a database of revoked token IDs and check it during verification. However, this adds a database lookup to every request, impacting performance.
Alternative approaches include:
- JWT ID (jti) and revocation: Include a unique token ID and check it against a revocation list
- Short expiration + rotation: Keep expiration short and rotate tokens frequently
- Session identifier: Include a session ID and invalidate sessions when needed
- Version tracking: Include a version number and increment it to invalidate all old tokens
For simple systems, relying on short expiration without explicit revocation is acceptable. For systems requiring user logout or session termination, implement one of these approaches.
Handling Expired Tokens in Frontend Applications
Frontend applications should detect when tokens expire and handle the situation gracefully. When an API returns a 401 Unauthorized response, the client should:
- Check if the access token is expired
- If expired, attempt to refresh using the refresh token
- If refresh succeeds, retry the original request with the new token
- If refresh fails (refresh token expired), redirect to login
This flow allows users to remain logged in as long as their refresh token is valid, transparently refreshing expired access tokens. Implement this in an HTTP interceptor:
// Example using axios
api.interceptors.response.use(
response => response,
error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
return refreshAccessToken()
.then(newToken => {
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return api(originalRequest);
})
.catch(() => {
// Refresh failed, redirect to login
window.location.href = '/login';
});
}
return Promise.reject(error);
}
);
Best Practices for JWT Expiration
Always set expiration times on tokens. Never create indefinitely valid tokens. At minimum, set some reasonable expiration, preferably relatively short.
Use the refresh token pattern for applications with human users. Short-lived access tokens paired with longer-lived refresh tokens provide optimal security and user experience.
Keep access token expiration short—typically 5 minutes to 1 hour depending on your risk tolerance. Shorter expiration times improve security significantly.
Keep refresh token expiration long enough for user convenience but not indefinitely. Consider 7 days to 30 days typical. Log out functionality should invalidate refresh tokens.
Synchronize server time using NTP. Don't rely on client-provided timestamps. The server determines what the current time is for expiration validation.
Don't implement custom clock skew handling beyond what your JWT library provides. Small clock tolerances are reasonable, but large tolerances compromise security.
Implement active revocation mechanisms for scenarios requiring logout before token expiration. Blacklisting or session invalidation handles these cases.
Conclusion
JWT expiration limits the lifetime of tokens, automatically restricting access if a token is compromised. Combined with the refresh token pattern, expiration enables both security and user convenience. Short-lived access tokens expire within minutes, limiting damage from compromise, while longer-lived refresh tokens maintain user sessions. Always set appropriate expiration times, implement the refresh token pattern for applications with users, and maintain server time synchronization. Expiration, combined with signature verification, creates a secure token-based authentication system.