Lập trình · 19/09/2026

Refresh Token Rotation in Practice: Secure API Authentication

A short-lived access token limits the useful lifetime of a leak, but a refresh token is a long-lived credential that can mint new access tokens. If a backend accepts the same refresh token repeatedly, a stolen copy can preserve access quietly. Refresh token rotation replaces the credential after every use and detects replay of an older token.

Refresh token rotation thực chiến: Thiết kế đăng nhập an toàn cho API

A short-lived access token limits the useful lifetime of a leak, but a refresh token is a long-lived credential that can mint new access tokens. If a backend accepts the same refresh token repeatedly, a stolen copy can preserve access quietly. Refresh token rotation replaces the credential after every use and detects replay of an older token.

Different responsibilities

  • Access token: short-lived, sent to a resource server, restricted by audience and scope.
  • Refresh token: sent only to the authorization server, longer-lived, and protected more carefully.

A refresh token does not need to be a JWT. An opaque random value is often easier to revoke and exposes no claims. Either format needs high entropy, TLS, and exclusion from URLs and logs.

How rotation works

  1. Login issues access token A1 and refresh token R1.
  2. The client exchanges R1.
  3. The server marks R1 used and returns A2 + R2.
  4. The relationship remains stored even though R1 is invalid.
  5. If R1 appears again, the server revokes the entire token family.

The server cannot know whether the attacker or legitimate client made the replay, so both lose the grant and must authenticate again.

Suggested data model

refresh_tokens
- id, family_id, user_id, client_id
- token_hash, parent_id, replaced_by_id
- status: active | used | revoked
- issued_at, expires_at, used_at, revoked_at
- scope, audience

Store only a deterministic hash of the random token. Never place raw refresh tokens in databases, APM traces, analytics, or audit logs.

Make refresh atomic

BEGIN;
SELECT * FROM refresh_tokens
WHERE token_hash = :hash FOR UPDATE;
-- reject expired/revoked
-- reused: revoke family and fail
-- active: mark used and insert R2
COMMIT;

A row lock or compare-and-swap prevents concurrent requests from both succeeding. Add unique constraints and indexes for token hash and family.

Handle legitimate races

Several API calls may notice an expired access token simultaneously. The client should use single-flight refresh: one shared promise performs the exchange while other calls wait. A server grace window weakens replay protection; if unavoidable, keep it very short and return the same replacement for the same client context rather than creating another branch.

Web and mobile storage

  • Web: prefer an HttpOnly; Secure; SameSite cookie plus appropriate CSRF defenses. Do not keep credentials in localStorage.
  • Mobile: use Keychain/Keystore and prevent token backup to another device.
  • BFF: let the browser hold only a session cookie while the backend-for-frontend manages OAuth tokens.

Cookies do not eliminate XSS or CSRF. Apply CSP, output encoding, Origin/CSRF checks, and restrictive CORS.

Expiration and revocation

Use both idle timeout and absolute lifetime. Revoke a family on device logout, password change, account suspension, detected reuse, or a risk event. Logout-all revokes every user family; logout-this-device revokes only the current family. Already issued access tokens may work until expiry unless resource servers use introspection or a denylist, so keep them short-lived.

API errors and observability

POST /oauth/token
grant_type=refresh_token&refresh_token=...

200 { access_token, expires_in, refresh_token }
400 { error: "invalid_grant" }

Do not reveal whether a token was expired, reused, or unknown to an untrusted client. Record the internal reason with family ID, client ID, and correlation ID, never the token.

Test checklist

  • A token succeeds exactly once.
  • Concurrent refreshes cannot create two valid branches.
  • Reusing an old token revokes its family.
  • Expired tokens and wrong clients, scopes, or audiences fail.
  • Logout and password changes block later refreshes.
  • Logs and traces contain no raw credentials.
  • Cookie, CSRF, and CORS behavior is tested in real browsers.

When to choose another approach

A same-domain web app may be simpler with server-side session cookies. High-risk systems can use sender-constrained tokens such as DPoP or mTLS, potentially alongside rotation. Avoid building an authorization server when a mature OAuth/OIDC provider or library meets the requirements.

Conclusion

Rotation is more than returning a new string. A secure design needs token families, use state, atomic replacement, reuse detection, revocation, and client-side single-flight behavior. Combined with short-lived access tokens and platform-appropriate storage, it gives a session a clear containment point when credentials leak.

References

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.