Skip to content
Refresh-Token Rotation + Reuse Detection
step 1/5

Reading — step 1 of 5

Read

~2 min readSecurity & Operations

Refresh-Token Rotation with Reuse Detection

Short access tokens (15 min) + long refresh tokens (days) is the dominant pattern. But long-lived refresh tokens can be stolen — laptop seized, browser extension compromised, log file leaked. Mitigation: rotation with reuse detection (OAuth 2.1 draft, Auth0, Okta).

The rules

  1. ISSUE: server hands the client a pair (access, refresh) in a new family (think: a chain ID).

  2. REFRESH: client presents refresh_n. Server returns (access_{n+1}, refresh_{n+1}) in the same family and marks refresh_n as USED.

  3. If refresh_n is presented a second time (i.e. its status is USED), that means someone already rotated past it. Either:

    • The user replayed by accident — harmless.
    • An attacker stole the refresh token, used it, and now the legitimate client is hitting the stale copy. Or vice versa.

    Either way, the server cannot tell — but it knows the family is compromised. Revoke the entire family. All access and refresh tokens issued in this chain are dead.

Why this works

Without rotation, a stolen refresh is useable indefinitely. With rotation

  • reuse detection, the attacker has at most until the legitimate client makes its next refresh — then the alarm fires, the user is logged out, and a session re-auth (password, MFA) re-establishes trust.

Refresh tokens are NOT JWTs (usually)

Refresh tokens are opaque random strings stored server-side. Why?

  • Revocation. JWTs are stateless; revocation requires a blacklist anyway, so why not store the token directly?
  • Refresh tokens get used rarely (once per access-token lifetime), so the DB hit is fine.

Some shops still use JWTs for refresh — fine, but add a jti and check it against the database every time, defeating the "no DB lookup" benefit of JWT.

What you'll build

A toy issuer/refresher with:

  • ISSUE user → new family
  • REFRESH rt_id → rotate or detect reuse
  • USE_ACCESS at_id → check current validity

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…