Refresh tokens and token rotation

Short-lived access tokens limit how long a stolen token is useful, but forcing a user to re-enter their password every 15 minutes isn't a workable product. Refresh tokens close that gap — and rotation is what stops a single stolen refresh token from becoming a permanent backdoor.

Intermediate

3 min read

The tradeoff access tokens create

A JWT access token is verified purely by its signature — no database lookup, no way to revoke it before it naturally expires. That's what makes it fast, and it's also what makes a stolen one dangerous: whoever has it can use it for its entire remaining lifetime, no matter what the server does afterward. The usual fix is making access tokens short-lived — 5 to 15 minutes — so a leaked token is only useful for a narrow window. But that alone would mean re-authenticating with a password every 15 minutes, which no real product ships.

What a refresh token actually is

A refresh token is a separate, long-lived credential (days to weeks) whose only job is minting new access tokens. It's opaque to the client and, unlike an access token, it's checked against server-side state on every use:

This splits the problem in two: the access token stays fast and stateless for every normal request, while the refresh token — used rarely — can afford a database check.

Rotation: detecting reuse

Refresh token rotation means every refresh exchange issues a brand-new refresh token and immediately invalidates the one just used. A refresh token becomes single-use. The payoff shows up specifically when a stolen refresh token gets used:

That last step — revoking the whole chain, not just the reused token — is what makes rotation worth implementing. It converts an undetectable, indefinite backdoor into a signal that forces both parties to re-authenticate, which is the closest thing to "the server noticed" that a stateless system can offer.

Where refresh tokens must live

A refresh token is more powerful than an access token — it's the thing that can mint new access — so it deserves the tightest storage available: an HttpOnly, Secure, SameSite cookie, never localStorage or a JS-readable value. HttpOnly means an XSS payload that runs in the page still can't read it, which matters more here than almost anywhere else, since the whole point of a short-lived access token is undermined if the refresh token sitting next to it is trivially stealable.

Revocation is still a database problem

Rotation handles theft-in-transit, but you still need an explicit "sign out everywhere" path — a user reporting a stolen laptop shouldn't have to wait for natural expiry. That means refresh tokens (or token families) need a server-side record that can be deleted on demand, which is the one piece of state a fully stateless JWT design can't avoid.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What problem do refresh tokens solve that short-lived access tokens create on their own?

2. In refresh token rotation, what happens when an already-used (consumed) refresh token is presented again?

3. Where should a refresh token be stored on the client, and why?

4. Why does refresh token rotation alone not provide a 'sign out everywhere' capability?