Key points
- Refresh tokens decouple session length from access-token TTL — short access tokens, longer refresh tokens.
- MUST be stored securely (httpOnly cookie, secure storage, encrypted DB) — leakage means session takeover.
- Refresh token rotation (issue a new RT on every use, invalidate the old one) detects theft (RFC 6749, OAuth 2.0 BCP).
- Public clients (SPAs, mobile) require PKCE + refresh token rotation + short RT lifetimes — and many security teams disable RTs in SPAs entirely.
- Revocable: the authorization server can invalidate a refresh token to terminate the session.
What is a refresh token?
A refresh token is the OAuth 2.0 credential that lets a client get a fresh access token without asking the user to sign in again. It's how "stay signed in for 30 days" works under the hood, and how server-to-server integrations stay connected across token expiries.
The pattern:
- User signs in. The authorization server returns an access token (short, e.g. 15 min) and a refresh token (longer, hours to days).
- The client uses the access token to call APIs.
- When the access token expires, the client calls
/tokenwithgrant_type=refresh_tokenand gets a new one. - If refresh token rotation is enabled, it also gets a new refresh token; the old one is invalidated.
Why rotation matters
Refresh tokens are long-lived. If one leaks (XSS, stolen device, malicious browser extension, stolen mobile backup) the attacker can keep refreshing forever — unless rotation is on. With rotation:
- Each refresh issues a new RT and invalidates the previous one.
- If the legitimate client and the attacker both try to use the same RT, the second use is detected as a refresh token reuse, and the entire token family is revoked — forcing both parties to re-authenticate.
This is mandatory for public clients per the OAuth 2.0 Security BCP.
Storage
- Confidential web app — refresh token lives server-side, never reaches the browser.
- Mobile — secure enclave / Keychain / Android Keystore.
- SPA — controversial. Best practice: don't use refresh tokens in the browser; use BFF (backend-for-frontend) pattern + httpOnly cookies. If you must, use rotation + short TTLs + DPoP.
When buyers care
- After incidents where stolen tokens enabled persistent access (LastPass, CircleCI, GitHub OAuth app compromise, midnight blizzard).
- When evaluating IdPs: do they support refresh token rotation? Configurable RT lifetime? Revocation API?
- For B2B integrations: how do you revoke a partner's access cleanly?
Editorial note
If a vendor's docs tell you to "store the refresh token in localStorage," close the tab. That's been a known anti-pattern for years.
