How do I handle JWT token expiration?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Handling JWT expiration correctly is mostly about separating access-token lifetime from session lifetime. A short-lived access token limits damage if it leaks, while a longer-lived refresh mechanism allows the user to stay signed in without constant reauthentication. The mistake is not token expiration itself. The mistake is building an expiration flow with no refresh plan or no revocation strategy.
Use Short-Lived Access Tokens
The exp claim tells the server when a token is no longer valid. Access tokens should usually be relatively short-lived.
A typical payload looks like this:
On the server, expired tokens should be rejected consistently. Do not silently accept them with ad hoc grace periods unless you have a clear reason and clock-skew handling policy.
Pair Them with Refresh Tokens
The standard solution is:
- short-lived access token
- longer-lived refresh token
- refresh endpoint that issues a new access token
That lets the application renew session continuity without turning the access token itself into a long-lived credential.
The access token and refresh token should also have different handling rules. Treating them as interchangeable credentials usually leads to a weaker design.
Store Tokens Safely
Where you store the token matters almost as much as how long it lives. A common web pattern is:
- access token kept in memory
- refresh token stored in a secure, HTTP-only cookie
That reduces direct JavaScript exposure for the long-lived credential while still allowing session renewal.
Handle Expiration on the Client Predictably
When an API returns 401 because the access token expired, the client should try one controlled refresh flow, then retry the original request if refresh succeeds.
Pseudo-flow:
- request fails with
401 - client calls refresh endpoint
- client stores new access token
- original request is retried once
Avoid letting many simultaneous requests all trigger independent refresh attempts. A single-flight refresh strategy is safer.
This is especially important in browser apps with many parallel API calls. Without coordination, several requests can all try to refresh at once and create race conditions in stored auth state.
Add Revocation and Rotation for Refresh Tokens
If refresh tokens are long-lived, they need stronger controls than access tokens. Useful practices include:
- rotate refresh tokens after use
- track them server-side
- revoke them on logout or suspected compromise
Without that, a stolen refresh token can quietly extend a session much longer than intended.
Handle Expiration as a Security and UX Problem
Expiration is not only a backend validation rule. It also affects user experience. If refresh fails, the client should clear auth state and send the user back to login cleanly rather than leaving the app in a half-authenticated state.
This is one of those cases where security and UX are closely linked. Broken refresh behavior often becomes broken authentication behavior.
Common Pitfalls
- Using very long-lived access tokens instead of proper refresh flow.
- Storing long-lived refresh tokens in insecure client-side storage.
- Treating any
401as a signal to refresh without checking the actual failure cause. - Allowing many concurrent refresh requests to race and overwrite each other.
- Forgetting logout-time revocation or refresh-token rotation.
Summary
- JWT expiration is easiest to handle with short-lived access tokens plus refresh tokens.
- Reject expired access tokens consistently on the server.
- Keep refresh tokens safer than access tokens and rotate or revoke them when appropriate.
- Implement a controlled client refresh flow instead of ad hoc retry loops.
- Treat expiration handling as both a security design problem and a user-session design problem.
Related reading
- How do I initialize the whitelist for Apache-Zookeeper?
- How do I load an HTTP URL with App Transport Security enabled in iOS 9?
- How do I prevent malicious DHT clients that might want to alter/delete my DHT data?
- How do I protect Python code from being read by users?
- How do I provide a username and password when running git clone email protected?
- How do I remove the passphrase for the SSH key without having to create a new key?
- How do I retrieve my MySQL username and password?
- How do I set GIT_SSL_NO_VERIFY for specific repos only?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.