Refresh Token Rotation Explained: Safer Long Sessions
Refresh token rotation issues a new refresh token on every use and revokes the old one, so a stolen token gets caught on reuse. How it works and pitfalls.
Refresh token rotation is a technique where every time a client uses its refresh token to get a new access token, the authorization server also issues a brand-new refresh token and invalidates the old one. Each refresh token works exactly once. If an attacker steals one and uses it, either the attacker or the legitimate client will eventually present a token that has already been used, and the server treats that reuse as evidence of theft and shuts down the whole session. It lets applications keep users signed in for long periods without leaving a long-lived, reusable credential lying around.
Why refresh tokens exist
Modern authentication usually splits credentials in two:
- Access tokens are short-lived, often minutes, and are sent with every API request. They’re frequently JWTs, validated by signature without a database lookup.
- Refresh tokens are longer-lived and are only ever sent to the authorization server’s token endpoint, to obtain a new access token when the current one expires.
The split limits damage. If an access token leaks, it stops working soon. But it moves the risk onto the refresh token: a stolen refresh token can mint fresh access tokens for as long as it remains valid, potentially days or weeks. Without rotation, the server has no way to tell the legitimate client and a thief apart, because both present the same valid token.
How rotation works
With rotation enabled, the refresh flow looks like this:
- The client sends refresh token
R1to the token endpoint. - The server validates
R1, marks it as used, and returns a new access token plus a new refresh tokenR2. - Next time, the client uses
R2, receivesR3, and so on.
All tokens descending from the original login form a token family. The server keeps track of which token in the family is currently valid.
The security payoff comes from reuse detection. Suppose an attacker steals R2:
- If the attacker uses
R2first, they receiveR3. When the real client later triesR2, the server sees a token that’s already been used. - If the real client uses
R2first, the attacker’s later attempt withR2is the reuse.
Either way, the server sees an already-redeemed token presented again. It can’t know which party is legitimate, so it does the safe thing: revoke the entire token family. Both parties lose access, and the real user has to sign in again. The theft is contained, and the event can be logged and alerted on.
Static vs rotating refresh tokens
| Static refresh token | Rotating refresh token | |
|---|---|---|
| Reuse | Can be used many times | Single use |
| Theft detection | None | Reuse revokes the family |
| Exposure window | Full token lifetime | Until next legitimate refresh |
| Server state | Token record or revocation list | Token family and current token |
| Client complexity | Low | Must store each new token reliably |
| Best for | Confidential server-side clients | Public clients: SPAs, mobile, desktop |
Confidential clients, such as a backend that authenticates to the authorization server with its own secret, already have strong protection because a stolen refresh token alone isn’t enough to redeem it. Rotation matters most for public clients, which can’t keep a secret. That’s why it’s commonly recommended alongside PKCE for single-page apps and native apps.
Absolute and idle lifetimes
Rotation alone could keep a session alive forever, since each new token resets the clock. Pair it with two limits:
- Idle timeout — if no refresh happens within a window, the family expires. Abandoned sessions die on their own.
- Absolute lifetime — no matter how active the session is, the family expires after a fixed maximum, forcing a fresh login.
These bounds are a policy decision based on the sensitivity of the application. A banking app and a recipe site shouldn’t share defaults.
The race-condition problem
The main operational headache with rotation is legitimate concurrency. Picture a single-page app open in two tabs, or a mobile app that fires several API calls at once right as the access token expires. Two requests try to refresh simultaneously with the same token. The first succeeds; the second looks exactly like reuse, and the whole family gets revoked. The user is logged out for no reason.
Common mitigations:
- Serialize refreshes on the client. Keep a single in-flight refresh promise and have every caller await it, rather than starting their own. In browsers, the Web Locks API can coordinate this across tabs.
- Grace period on the server. Allow the just-replaced token to be redeemed again for a few seconds, returning the same successor rather than revoking. This trades a sliver of security for robustness.
- Handle network failures. If the server rotated the token but the response never reached the client, the client still holds the old one. A short grace period also covers this case.
Where to store refresh tokens
Rotation reduces the impact of theft; it doesn’t make storage unimportant. Guidance by platform:
- Browser apps: prefer keeping refresh tokens out of JavaScript entirely, for example with a backend-for-frontend that holds tokens server-side and gives the browser a session cookie with the HttpOnly, Secure, and SameSite attributes. If the token must live in the browser, rotation becomes essential, since cross-site scripting can read anything JavaScript can.
- Mobile and desktop: use the platform’s secure storage, such as the OS keychain or keystore.
For an even stronger guarantee, DPoP binds tokens to a key pair held by the client, so a stolen token is useless without the private key. Rotation and sender-constraining work well together.
Implementing it
Most identity providers offer rotation as a configuration setting, usually with reuse detection and a configurable grace window. If you’re building your own authorization server, the core requirements are:
- Store a hash of each refresh token, never the raw value.
- Record the family ID, the token’s parent, and whether it’s been used.
- Make the “check unused, mark used, issue successor” step atomic, so two simultaneous requests can’t both succeed.
- On reuse outside any grace period, revoke every token in the family and log a security event.
To inspect what’s inside the access tokens your provider issues, paste one into the JWT decoder. Refresh tokens are often opaque random strings, deliberately carrying no readable claims.
The takeaway
Refresh token rotation makes every refresh token single-use: each redemption returns a new one and retires the old. Presenting a retired token signals theft, and the server revokes the whole token family. Use it for public clients like SPAs and mobile apps, bound it with idle and absolute lifetimes, serialize client-side refreshes or allow a short server grace period to avoid false logouts, and keep the tokens themselves in the most protected storage your platform offers.
Keep reading
Chisato · · 5 min read What Is DPoP? Sender-Constrained OAuth Tokens Explained
DPoP binds OAuth access tokens to a client-held key, so a stolen token is useless without the private key. How proofs work and when to use DPoP.
Chisato · · 5 min read What Is an Open Redirect? Risks and How to Prevent It
An open redirect lets attackers use your domain to send users to any URL. How open redirect vulnerabilities work, why they matter, and how to fix them.
Chisato · · 4 min read What Is a Man-in-the-Browser Attack?
A man-in-the-browser attack uses malware inside the browser itself to alter what a user sees and submits, bypassing HTTPS and session protections entirely.