Skip to content
Ashish.
All posts
Diagram illustrating the three pillars of JWT security: Expiration, Rotation, and Revocation.

JWT Expiration, Rotation, and Revocation: A Lifecycle Guide

A guide to JWT expiration, rotation, and revocation strategies for secure token lifecycle management.

By Ashish Srivastava

JWT Expiration, Rotation, and Revocation

The fundamental flaw in most JWT implementations is treating the token as a permanent key. In reality, a JWT is a time-bound credential that must be treated as disposable. When an attacker steals a token, they gain access until that token expires. The goal of token lifecycle management is not just to make the token expire, but to ensure that if it is stolen, it becomes useless immediately or within a negligible window. This requires three distinct mechanisms: expiration, rotation, and revocation.

The Expiration Mechanism: The Clock Check

Expiration is the simplest defense, implemented via the exp (expiration time) claim defined in RFC 7519. This claim is not a timer on the client; it is a constraint the server checks during every request.

When a request arrives with a JWT, the server performs a cryptographic signature verification. If the signature is valid, the server parses the payload and compares the current server time against the exp timestamp. If now > exp, the token is rejected. This is a stateless check; the server does not need to query a database to know if the token is expired.

However, relying solely on expiration creates a window of vulnerability. If a token has a 24-hour lifespan and is stolen at minute 1, the attacker has 23 hours of access. Furthermore, this mechanism assumes the server and client clocks are synchronized. If the client's clock is fast, they might accept a token that the server considers expired; if the client's clock is slow, the server might reject a valid token.

To mitigate this, implementations often allow a small skew tolerance, but this is an opinionated trade-off. A more effective approach is to reduce the access token lifespan significantly (e.g., 15 minutes) and rely on a refresh token for renewal. The access token should never hold long-term privileges.

Rotation: The Sliding Window Defense

Rotation addresses the "stolen token" problem that expiration alone cannot solve. The standard pattern is Refresh Token Rotation. In this model, the refresh token is short-lived but paired with the access token.

Consider a scenario with two actors: Alice (the client) and the Auth Server.

  1. Alice logs in. The server issues an Access Token (AT) valid for 15 minutes and a Refresh Token (RT) valid for 7 days.
  2. Alice uses the AT to access the API. It expires.
  3. Alice sends the RT to the /refresh endpoint.
  4. Crucial Step: The server issues a new Access Token and a new Refresh Token. The old Refresh Token is immediately invalidated in the database.

This mechanism prevents replay attacks. If an attacker intercepts Alice's refresh token, they can use it once. The moment they do, the server invalidates that specific RT ID and issues a new one. When Alice's legitimate client tries to use the old RT (which was intercepted), the server rejects it because it no longer exists in the active store. The attacker is locked out immediately after their first attempt.

This strategy is explicitly recommended by the OWASP Authentication Cheat Sheet. It relies on the server maintaining state for refresh tokens, which is a necessary trade-off for security.

Revocation: Breaking the Connection

Expiration and rotation handle the normal lifecycle, but they fail when a user explicitly logs out or when a device is compromised and needs immediate termination. This requires revocation.

In a purely stateless system, revoking a JWT is impossible because the token contains all necessary data. You cannot "unsign" a token or invalidate a hash without a central lookup. Therefore, revocation introduces state.

There are two primary mechanisms for revocation:

  1. Blacklisting (Denylist): The server maintains a list of revoked token IDs (usually the jti claim) in a fast store like Redis. On every request, the server checks if the jti exists in the blacklist.

    • Mechanism: High latency. Every API request must hit the cache.
    • Trade-off: Infinite validity window for the blacklist entry, but high operational cost.
  2. Short-Lived Revocation List: Instead of storing revoked tokens indefinitely, the server stores a list of recently revoked tokens. Once the access token's natural expiration time passes, the revocation check becomes irrelevant because the token is already dead.

    • Mechanism: The revocation list only needs to persist for the duration of the access token's life (e.g., 15 minutes).
    • Trade-off: This reduces the memory footprint of the blacklist significantly.

If a user logs out, the server adds the current jti to the Redis set. If an attacker has a copy of the token, they can still use it until the 15-minute window closes, at which point the token expires naturally. This hybrid approach balances immediate revocation capability with performance.

The Combined Strategy

A secure implementation combines these mechanisms into a cohesive flow. The access token remains stateless and short-lived (15 minutes). The refresh token is stateful and rotates on every use. Revocation is handled via a short-lived Redis set.

  1. Login: User authenticates. Server creates access_token (15m) and refresh_token (7d). Both IDs are stored in Redis with a TTL of 7 days for the RT, and 15 minutes for the RT's "revocation window" check.
  2. Refresh: Client sends refresh_token. Server validates signature. Server checks Redis for the RT ID.
    • If present: Issue new access_token and new refresh_token. Delete old RT ID from Redis.
    • If absent: Reject (token not found, expired, or already rotated/revoked).
  3. Logout: Client calls /logout. Server adds current access_token jti to the "revoked" set in Redis with a TTL of 15 minutes (matching the access token life).

This architecture ensures that even if a token is stolen, the window of opportunity is limited by the short access token lifetime, and the attacker cannot reuse the refresh token after the first failed rotation attempt. The revocation list ensures that explicit user actions take effect immediately, closing the gap between theft and expiration.

Implementation Considerations

When implementing this, the choice of storage for the refresh token is critical. A relational database is too slow for the high-frequency checks required by rotation. An in-memory store like Redis is standard. However, you must ensure the Redis instance is secured; if the Redis instance is compromised, the attacker can bypass the rotation logic entirely.

Additionally, for this specific rotation and revocation strategy, the jti claim is mandatory. Ensure your JWT library supports generating and validating this custom claim. Without a unique identifier per token instance, you cannot distinguish between a legitimate rotation and a replay attack. Every time a token is issued, the jti must change.

Finally, consider the "sliding window" behavior. If a refresh token is valid for 7 days but the user hasn't used it for 6 days and 23 hours, does the token expire at the 7-day mark, or does the last use extend it? For security, the absolute expiration (issued at + 7 days) is preferred over a sliding window, preventing long-term dormant tokens from being kept alive indefinitely by infrequent use.

The complexity of managing state for refresh tokens and revocation lists is the price paid for security. Stateless JWTs are convenient, but they are not secure for high-value applications without these additional controls. The mechanism of rotation transforms the token from a static key into a dynamic credential, ensuring that the theft of one piece of data does not compromise the entire session.

Pitfalls

Implementing token lifecycle management introduces specific risks if not executed with precision.

  1. Storing Secrets in Local Storage: Storing refresh tokens in browser localStorage exposes them to Cross-Site Scripting (XSS) attacks. An attacker can easily script a read of localStorage to exfiltrate the token. Prefer HttpOnly cookies for refresh tokens to prevent JavaScript access, or use secure, isolated storage mechanisms.
  2. Long-Lived Refresh Tokens Without Rotation: Issuing a refresh token valid for months or years without enforcing rotation creates a massive window of opportunity. If that token is stolen, the attacker retains access for the entire duration. Always pair long-lived refresh tokens with strict rotation policies to limit exposure.
  3. Failing to Secure the Redis Instance: The security of the rotation and revocation logic relies entirely on the confidentiality of the Redis store. If the Redis instance is exposed to the public internet or lacks authentication, an attacker can read the active refresh tokens, bypassing the entire rotation defense. Ensure Redis is bound to localhost or a private network and requires strong authentication.

Practical Takeaways

To internalize these concepts, consider the following mental models:

  • Access Tokens are Cash: Treat them like physical cash. They are valuable but should be spent quickly and replaced often. If lost, the loss is limited to the immediate transaction window.
  • Refresh Tokens are ATM Cards: These are the keys to getting more cash. They must be guarded closely. If the card is stolen, the bank (server) must be able to cut it immediately so the thief cannot withdraw more funds.
  • Revocation is the Fire Alarm: It is the emergency stop. While expiration and rotation handle routine lifecycle events, revocation is the specific action required when a breach is confirmed or a user explicitly requests termination.

FAQ

Can I revoke a token? Yes, but not in a purely stateless manner. You must introduce a state store (like Redis) to maintain a blacklist of revoked token IDs (jti). The server must check this store on every request to deny access to revoked tokens.

How long should refresh tokens last? There is no single correct answer, but they should be long enough for user convenience yet short enough to minimize risk. Common patterns range from 7 to 30 days. Crucially, they must be rotated on every use to prevent replay attacks, effectively limiting the "stolen token" window to the time between the theft and the next legitimate refresh attempt.

Is Redis mandatory? Redis is the industry standard for this use case due to its speed and TTL (Time To Live) support, but it is not strictly mandatory. Any fast key-value store or database capable of handling high-frequency reads/writes and expiring keys can serve the same purpose, though Redis is generally preferred for performance.

Conclusion

Securing JWTs requires moving beyond simple stateless validation. By decoupling the short-lived access token from the long-lived refresh token, and enforcing a rotation strategy coupled with a short-lived revocation list, developers can significantly reduce the impact of token theft. While this introduces state management overhead, it is the required cost for robust token lifecycle management in modern applications.

Related posts