Why Refresh Tokens Exist
Access tokens are deliberately short-lived, often 5 to 60 minutes, so that if one leaks, the exposure window is small. But making a user log in again every hour would be unusable. Refresh tokens solve this: issued alongside the access token (typically only for Authorization Code and Device Code flows), a refresh token is a longer-lived credential the client can present to /token with grant_type=refresh_token to obtain a fresh access token, and often a fresh refresh token too, without the user seeing another login screen.
Cricket analogy: It's like a season ticket at a cricket ground: your matchday entry pass (the access token) expires after each game, but the season ticket itself (the refresh token) lets you print a new matchday pass for the next fixture without re-registering.
Rotation and Reuse Detection
Modern best practice is refresh token rotation: every time a refresh token is used, the server invalidates it and issues a brand new refresh token alongside the new access token, so each refresh token is single-use. This lets the server detect theft: if an old, already-used refresh token is ever presented again, that's a strong signal the token was stolen and used by an attacker in parallel with the legitimate client, and the server should immediately revoke the entire token family, forcing re-authentication for everyone holding a token from that lineage.
Cricket analogy: It's like a stadium requiring each entry wristband to be cut off and replaced with a new one at every re-entry; if security ever sees someone try to reuse a wristband already marked 'cut', it's proof of a forged duplicate and the whole gate's access is locked down.
Storage and Lifetime Policy
Refresh tokens are the most sensitive artifact in an OAuth deployment because they often live for days, weeks, or indefinitely (until revoked), unlike short-lived access tokens. For confidential clients, they should be stored server-side, never in browser localStorage or a mobile app's plain shared preferences; for SPAs, the recommended pattern is a backend-for-frontend (BFF) that holds the refresh token server-side and only ever hands the browser a short-lived, httpOnly session cookie. Authorization servers typically also enforce an absolute lifetime (say, 30 or 90 days) alongside the rolling per-use expiry, so a token can't be kept alive forever purely through automated refresh activity.
Cricket analogy: It's like a franchise storing a player's multi-year central contract documents in a locked HR vault rather than handing the physical contract to the player to carry around, only ever giving the player a daily access badge derived from it.
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=v1.MzY4YzQ4NzYtOTFhZS00...
&client_id=mobile-app-789
# Response — note the rotated refresh_token
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "v1.NDIxYzU5ODctODJiZi01...",
"scope": "orders:read orders:write"
}
# If the OLD refresh_token above is ever replayed after this point,
# the server should revoke the entire token family:
HTTP/1.1 400 Bad Request
{ "error": "invalid_grant", "error_description": "token reuse detected" }The refresh_token grant is only issued for flows that had a genuine resource owner and consent step — chiefly Authorization Code (with or without PKCE) and Device Code. Client Credentials never gets one, because the client can simply re-authenticate with its own secret whenever it needs a new access token.
Common Refresh Token Mistakes
The most frequent production mistake is treating a non-rotating, long-lived refresh token as safe to store in browser localStorage; localStorage is readable by any JavaScript running on the page, so a single XSS vulnerability anywhere in the app hands an attacker a token that can mint fresh access tokens indefinitely. A second common mistake is failing to implement reuse detection at all, silently accepting any valid refresh token forever, which means a leaked token from months ago, say, from an old log file, still works and nobody is alerted when it's used.
Cricket analogy: It's like leaving the pavilion's master key under the doormat because it's convenient; anyone who finds it, not just the groundskeeper, can let themselves in indefinitely with no alarm ever triggered.
Never store a refresh token in browser localStorage or sessionStorage for a public client (SPA). Both are trivially readable by any script on the page, so a single XSS bug becomes a permanent account takeover. Use the backend-for-frontend pattern: the refresh token stays server-side, and the browser only ever holds a short-lived httpOnly, SameSite cookie.
- Refresh tokens let clients obtain new access tokens without repeating the full user-facing authorization flow.
- Only user-facing flows (Authorization Code, Device Code) issue refresh tokens; Client Credentials does not.
- Rotation issues a brand new refresh token on every use and invalidates the old one, making each refresh token single-use.
- Reuse of an already-rotated refresh token is a strong theft signal and should trigger revocation of the entire token family.
- Refresh tokens must never be stored in browser localStorage for public clients; use server-side storage via a backend-for-frontend.
- Authorization servers typically enforce both a rolling expiry (per use) and an absolute maximum lifetime for refresh tokens.
- Failing to implement reuse detection means a leaked refresh token from the past can still be used silently, with no alert.
Practice what you learned
1. Why are access tokens deliberately kept short-lived while refresh tokens are longer-lived?
2. What does refresh token rotation mean in practice?
3. What should an authorization server do if an already-rotated (previously used) refresh token is presented again?
4. Why does the Client Credentials grant never issue a refresh token?
5. What is the recommended pattern for handling refresh tokens in a single-page application?
Was this page helpful?
You May Also Like
Authorization Code with PKCE
The Authorization Code grant is OAuth 2.0's most secure and widely used flow, and PKCE hardens it further so it is safe for public clients like single-page apps and mobile apps.
The Client Credentials Grant
The Client Credentials grant lets a service authenticate as itself, without any user in the loop, making it the standard choice for machine-to-machine and backend-to-backend API access.
Choosing the Right Grant
OAuth 2.0 offers several grant types built for different trust boundaries; picking the wrong one is one of the most common real-world sources of OAuth vulnerabilities.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics