
Refresh Token Rotation and Reuse Detection
An examination of refresh token rotation and reuse detection mechanisms to secure identity systems against replay attacks and race conditions.
In modern identity architectures, the separation of access tokens (short-lived) and refresh tokens (long-lived) is standard practice. However, treating refresh tokens as static, reusable credentials introduces a critical vulnerability: if an attacker intercepts a refresh token, they can maintain access indefinitely. Refresh Token Rotation (RTR) and Reuse Detection mitigate this by introducing statefulness to the refresh process, ensuring that stolen tokens are quickly invalidated.
The Threat Model: Static vs. Dynamic Credentials
A static refresh token acts like a master key. If an attacker captures it via network sniffing, XSS, or log leakage, they can use it repeatedly until it expires or is manually revoked. This is a replay attack.
RTR changes the model. Instead of a single key, the system manages a "token family." Each time a client uses a refresh token, the server issues a new refresh token and invalidates the previous one. This ensures that only the most recently issued token is valid. If an attacker has an older token from the family, it becomes useless immediately after the legitimate client rotates it.
Mechanism of Rotation
The rotation process follows a strict protocol defined in the OAuth 2.1 specification (currently an Internet-Draft, though the mechanisms align with its proposed direction). The client sends the current refresh token to the token endpoint. The server validates it, issues a new access token and a new refresh token, and then marks the old refresh token as invalid.
Consider a client application app-client interacting with an identity provider auth-server.
POST /token HTTP/1.1
Host: auth-server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=rt_abc123&client_id=app-clientUpon successful validation, the server responds with:
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "at_xyz789",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_def456",
"scope": "read write"
}Crucially, the server must invalidate rt_abc123 immediately. If the client receives rt_def456, it must discard rt_abc123 and use rt_def456 for all future requests. This creates a linear chain of tokens: rt_abc123 -> rt_def456 -> rt_ghi789. Only the head of the chain is valid.
Race Conditions and Reuse Detection
The primary challenge with RTR is handling race conditions. Suppose an attacker has stolen rt_abc123. Simultaneously, the legitimate client is using rt_abc123 to request a new token. Two scenarios can occur:
- Legitimate Client Wins: The server processes the legitimate client’s request first, issues
rt_def456, and invalidatesrt_abc123. When the attacker tries to usert_abc123, the server rejects it with aninvalid_granterror. The attacker is locked out. - Attacker Wins: The attacker uses
rt_abc123first. The server issuesrt_def456and invalidatesrt_abc123. The legitimate client then attempts to usert_abc123, which is now invalid. The legitimate client receives aninvalid_granterror.
Scenario 2 is a denial-of-service condition for the legitimate user. However, it also triggers Reuse Detection. The server recognizes that rt_abc123 was already used (by the attacker) and is now being presented again. This indicates a potential theft.
To handle this, servers must implement reuse detection logic. When a refresh token is presented, the server checks if it has been used before. If yes, it does not just reject the request; it revokes the entire token family associated with the user session. This ensures that both the attacker’s newly issued token (rt_def456) and any future tokens are invalidated, forcing the user to re-authenticate.
Common Pitfalls
Implementing RTR and reuse detection requires careful consideration of database performance and locking strategies.
Database Locking: To prevent race conditions, the server must atomically check and invalidate the refresh token. This typically involves a database transaction with row-level locking. For high-throughput systems, this can become a bottleneck. An alternative is to use a distributed lock or a message queue to serialize invalidation events, but this adds complexity.
Performance: Storing and managing a history of refresh tokens for reuse detection increases storage requirements. Some implementations use a bloom filter or a hash-based approach to quickly check if a token has been used before, without storing the full history. However, this may increase false positives. A more common optimization is to mark each refresh token as consumed via a single boolean flag when it is exchanged, rather than storing full history. This mitigates performance concerns by requiring only a quick flag check on each request, immediately flagging any reuse of an already-consumed token as suspicious, without needing to store a full list of used tokens.
User Experience: Frequent reuse detection events can lead to unexpected logouts for users. To mitigate this, servers can implement a grace period where a single reuse of a token is allowed (issuing a new token) but flags the session for monitoring. Subsequent reuses trigger full revocation. This balances security with usability.
Conclusion
Refresh Token Rotation and Reuse Detection are essential components of a secure identity system. They transform static credentials into dynamic, stateful tokens, significantly reducing the window of opportunity for attackers. By implementing these mechanisms, developers can protect against replay attacks and race conditions, ensuring that stolen tokens are quickly invalidated and users are not unnecessarily disrupted.
The key is to treat refresh tokens as ephemeral resources, not permanent keys. By rotating them and detecting reuse, you create a defense-in-depth strategy that adapts to the threat landscape.
Practical Takeaways
- Assume Theft: Design your system as if every refresh token will eventually be compromised. Rotation and reuse detection are your primary defenses.
- Atomicity is Non-Negotiable: Ensure that the check-for-reuse and the issuance of a new token happen within a single atomic transaction or lock scope to prevent race conditions.
- Grace Periods Save UX: Implement a "grace period" for reuse detection. Allow one immediate retry to handle legitimate race conditions, but revoke the entire family on the second occurrence.
FAQ
What happens if rotation fails due to a database error?
If rotation fails, the server should return an invalid_grant error. The client must then treat the current refresh token as invalid and force the user to re-authenticate. Do not attempt to retry rotation silently, as this could mask underlying infrastructure issues or security events.
How to handle offline clients or mobile apps with background sync? Offline clients may hold stale refresh tokens. Implement a "sliding expiration" where the token remains valid as long as the user is active, but enforce absolute maximum lifetimes. Additionally, track metadata such as IP address or device fingerprint at each token exchange to detect if a token is being used from a different location or device than expected, which might indicate theft rather than simple staleness.
Is it safe to store refresh tokens in local storage?
No. Storing refresh tokens in localStorage exposes them to XSS attacks. Use httpOnly, secure, SameSite=Strict cookies for refresh tokens whenever possible. If you must use JavaScript-accessible storage, ensure you have robust XSS protections and consider using short-lived access tokens with a separate, more secure mechanism for obtaining new ones.
Related posts
OIDC Dynamic Client Registration (RFC 7591)
A technical examination of RFC 7591, covering dynamic client registration, initial access tokens, and automated client management for backend systems.
Spring Boot Keycloak: The Current Way
A guide to securing Spring Boot applications with Keycloak using the current OAuth2 Resource Server and JWT Decoder standards, replacing deprecated adapters.
Keycloak Zero Trust Configuration
Implement zero trust security in Keycloak using client policies, step-up authentication, token binding, and session evaluation.