Access tokens and refresh tokens are issued together and often stored together, yet a stolen access token buys an attacker minutes while a stolen refresh token can buy months. This guide compares the two OAuth 2.0 credentials property by property: who sees each one, how long it should live, where it should be stored in web, mobile and server clients, and what happens when each is stolen. It gives a short answer first, then decision guidance by client type, the controls that make the pair safe together, and the mistakes that turn a refresh token into a long-lived master key. It is for application developers, API owners and identity architects setting token policy. Rotation mechanics and lifetime tiers have their own pages: see the OAuth Refresh Token Rotation and Expiry Guide and the Token Lifetime Policy Design Guide.
Key takeaways
- They talk to different servers. RFC 6749 section 1.5 says refresh tokens are intended for use only with authorisation servers and are never sent to resource servers. The access token goes to the API; the refresh token goes only to the token endpoint.
- Short access, bounded refresh. RFC 6750 recommends bearer access tokens of one hour or less. Refresh tokens can last longer, but RFC 10017 and OWASP ASVS 5.0 (V10.4.8) require an absolute expiry, even when rotation slides the idle window.
- Theft impact is not symmetric. A stolen access token works until
exp. A stolen bearer refresh token mints fresh access tokens until it expires or is revoked, which is why RFC 9700 requires public clients to rotate or sender-constrain them. - Not every flow needs a refresh token. RFC 6749 says a refresh token SHOULD NOT be issued for client credentials; a machine client simply requests a new access token. Issue refresh tokens only where a user is not present to re-authorise.
- Revoke from the refresh side. Under RFC 7009, revoking a refresh token SHOULD also invalidate access tokens from the same grant where the server supports it; revoking only an access token leaves the attacker able to mint another.
The short answer
An access token is the credential an API accepts. RFC 6749 section 1.4 defines access tokens as credentials used to access protected resources; the client presents one on each call, usually as Authorization: Bearer <token>, and the resource server checks it, either locally (a signed JWT, profiled in RFC 9068) or by asking the authorisation server (introspection, RFC 7662). It should be short-lived, narrowly scoped and audience-restricted, because every API, gateway, proxy and log on the request path may see it.
A refresh token is a credential the client uses to get a new access token without asking the user again. It is sent only to the authorisation server's token endpoint with grant_type=refresh_token. The authorisation server decides whether to issue one at all, and under RFC 6749 section 6 a refreshed access token can never carry scope that was not originally granted. It lives longer and so must be protected more strongly, but it is exposed on far fewer network paths.
The design rule follows: keep the widely shared credential short-lived, and keep the long-lived credential narrowly shared, bound to the client and revocable.
Side-by-side comparison
| Property | Access token | Refresh token |
|---|---|---|
| Purpose | Authorise calls to a resource server (API) | Obtain new access tokens (and sometimes a new refresh token) from the authorisation server |
| Presented to | Resource servers, gateways and any proxy on the path | Only the authorisation server token endpoint (RFC 6749 section 1.5) |
| Typical lifetime | Minutes; RFC 6750 recommends one hour or less for bearer tokens | Hours to weeks, with idle and absolute expiry; tied to the user's session in browser apps (RFC 10017) |
| Format | Opaque reference or signed JWT (RFC 9068) | Normally opaque, readable only by the authorisation server |
| Validation | Signature and claims locally, or introspection (RFC 7662) | Lookup at the authorisation server, plus client authentication for confidential clients |
| Client authentication on use | None: the bearer is trusted | Required for confidential clients (RFC 6749 section 6); public clients rely on rotation or binding |
| Issued for client credentials grant | Yes | SHOULD NOT be (RFC 6749 section 4.4.3) |
| Theft impact | Access until exp, limited to its scope and audience | Repeated minting of access tokens until expiry, rotation reuse detection or revocation |
| Revocation | Effective at once only if the API introspects; JWTs stay valid until exp | Immediate at the authorisation server (RFC 7009), and SHOULD cascade to access tokens from the same grant |
| Proof of possession options | DPoP (RFC 9449) or mTLS certificate binding (RFC 8705) | Same mechanisms; RFC 9700 requires binding or rotation for public clients |
A useful way to read the table: the access token's risk comes from how many places it travels, the refresh token's risk from how long it lasts. Controls for each target that specific exposure.
Lifetimes: setting each one
- Access token lifetime: start at 5 to 15 minutes for high-value APIs and no more than 60 minutes for general use, in line with RFC 6750. Shorter lifetimes reduce the value of a stolen token and the gap between a revocation and its effect on APIs that validate JWTs locally. The cost is more token endpoint traffic, which is cheap compared with introspecting every call.
- Refresh token idle expiry: RFC 9700 section 4.14.2 says refresh tokens should expire after a period of client inactivity. A refresh token not used for, say, 7 to 14 days on a consumer app or 24 hours on an administrative console should die on its own.
- Refresh token absolute expiry: OWASP ASVS 5.0 V10.4.8 requires an absolute expiration even when sliding expiration is applied. RFC 10017 adds that for browser apps a rotated refresh token must not extend the original lifetime: an 8-hour refresh token shrinks with each rotation, and the user re-authenticates after 8 hours.
- Session binding: RFC 10017 says browser refresh tokens should be tied to the user's session at the authorisation server, so logout there ends them. RFC 9700 allows automatic revocation on security events such as a password change or logout.
- Scope binding: RFC 9700 says issued refresh tokens MUST be bound to the scope and resource servers the resource owner consented to. A refresh request may narrow scope, never widen it.
| Client type | Access token | Refresh token | Main control on the refresh token |
|---|---|---|---|
| Server-side web app (confidential client) | 5 to 60 minutes | Issued; idle and absolute expiry matched to the app session | Client authentication on every refresh; server-side storage |
| Browser single-page app with a backend for frontend | Held by the backend only | Held by the backend only | Tokens never reach the browser; HttpOnly, Secure session cookie (RFC 10017) |
| Browser single-page app without a backend | Short, in memory | Only with rotation or DPoP, with a maximum lifetime that rotation does not extend | Rotation with reuse detection, or DPoP binding |
| Native mobile or desktop app (public client) | Short | Issued; longer idle window for usability, absolute cap | Rotation or DPoP; platform secure storage |
| Machine client (client credentials) | Short; request a new one when it expires | Not issued | Protect the client credential instead |
| Third-party SaaS integration acting for an organisation | Short | Issued, often long-lived by design | Inventory, idle expiry, IP or network restrictions where the platform offers them, fast revocation |
Storage: where each token should live
Storage follows exposure. The access token has to be reachable by whatever code calls the API. The refresh token has to be reachable only by whatever code calls the token endpoint, which can usually be a much smaller and better protected component.
- Browser apps: RFC 10017 (OAuth 2.0 for Browser-Based Applications, published as BCP 212 in August 2026) explains that malicious JavaScript runs with the same privileges as the application and can read origin storage such as
localStorageand IndexedDB. Its strongest pattern is the backend for frontend (BFF): a confidential client on the server holds both tokens and the browser holds only a session cookie, which the BFF MUST markSecureandHttpOnly, withSameSite=Strictand a__Host-style prefix recommended. OWASP ASVS 5.0 V10.1.1 points the same way: tokens only to components that strictly need them. See the OAuth for Browser-Based Applications and the BFF Pattern Guide and the Browser Storage Security Guide. - Native apps: keep the refresh token in the operating system's protected credential store (Keychain on Apple platforms, Keystore-backed storage on Android) and the access token in memory. See the OAuth for Native and Mobile Apps Guide.
- Server-side apps and integrations: encrypt refresh tokens at rest, keep them in a secrets store or an encrypted database column with access limited to the token-refresh component, and never copy them into logs, support tickets, analytics or backups that are less protected than the primary store. RFC 6749 section 10.4 says refresh tokens MUST be kept confidential in transit and storage.
- Anywhere: never put either token in a URL. RFC 9700 section 4.3.2 says clients MUST NOT pass access tokens in a URI query parameter, because URLs end up in browser history, proxy logs and referrer headers. See the JWT and Session Tokens in Code and Logs Guide.
Theft risk: what an attacker gets from each
| Stolen credential | What the attacker can do | What limits the damage |
|---|---|---|
| Bearer access token | Call the APIs in its aud with its scopes until exp | Short lifetime, narrow scope and audience, DPoP or mTLS binding, introspection-based revocation |
| Bearer refresh token, public client, no rotation | Mint new access tokens from anywhere until the refresh token expires or is revoked | Idle and absolute expiry, revocation on logout or password change |
| Bearer refresh token, public client, rotation on | Race the legitimate client; the first reuse of an old token alerts the server, which revokes the token family | Reuse detection: RFC 9700 notes this stops the attack at the cost of the legitimate client re-authorising |
| Sender-constrained refresh token (DPoP or mTLS) | Nothing without the client's private key | Key kept in non-exportable storage where the platform offers it |
| Refresh token plus client secret of a confidential client | Mint access tokens as that integration, often for many tenants at once | Asymmetric client authentication, network restrictions, inventory and fast bulk revocation |
The last row is how SaaS integration breaches scale. In the Salesloft Drift breach, OAuth access and refresh tokens held in the integration provider's environment were used to export customers' Salesforce data between 8 and 18 August 2025, until all access and refresh tokens for the application were revoked on 20 August 2025. The integration's stored refresh tokens, not any single access token, were what gave the attacker repeatable access.
ATT&CK tracks the theft as T1528 Steal Application Access Token and the reuse as T1550.001 Use Alternate Authentication Material: Application Access Token. See the MITRE ATT&CK T1528 Steal Application Access Token Guide.
When to use which: decision guidance
- Is a user present to re-authorise cheaply? If the user is at a browser with a live single sign-on session, a silent redirect to the authorisation server can often replace a refresh token. If the app must keep working while the user is away (mobile apps, offline sync, scheduled jobs on a user's behalf), issue a refresh token.
- Is the client a machine with its own credential? Use the client credentials grant and request a new access token when needed. No refresh token. See the Machine-to-Machine OAuth (Client Credentials) Guide.
- Can the client keep a secret or a key? Confidential clients authenticate on every refresh, which already constrains a stolen refresh token. Public clients cannot, so RFC 9700 section 2.2.2 says their refresh tokens MUST be sender-constrained or rotated.
- How sensitive is the API? For payments, administration and bulk data export, shorten the access token, require DPoP or mTLS, and consider no refresh token at all, forcing step-up re-authentication. See the DPoP Guide.
- Who holds the tokens: you or a third party? When a vendor's integration holds refresh tokens to your tenant, you cannot control their storage. Limit the grant's scope, apply idle expiry and network restrictions in your platform where available, and review the integration list regularly.
Small organisations: use the defaults of your identity provider, confirm that refresh token rotation and an absolute lifetime are on for public clients, and adopt a BFF library for browser apps rather than building token storage yourself. Large organisations: publish a token policy per client type (the table above is a starting point), enforce it in the authorisation server configuration, and add refresh token inventory to third-party integration reviews.
How they work together safely
- Configure the authorisation server first: set access token lifetime per API audience, enable refresh token rotation with reuse detection for public clients, set idle and absolute refresh lifetimes, and disable refresh tokens for the client credentials grant.
- Bind where you can: enable DPoP for public clients that support it, and mTLS certificate-bound tokens for confidential server-to-server clients. RFC 9700 says authorisation and resource servers SHOULD sender-constrain access tokens.
- Validate correctly at every API: check signature,
iss,aud,expand scope; reject anything that is not an access token (OWASP ASVS 5.0 V9.2.2 asks services to validate token type and purpose). A refresh token or ID token presented to an API is an error, not a credential. - Test in a pilot ring: shorten lifetimes for one client or tenant, watch refresh error rates and user re-authentication prompts, and fix clients that do not refresh before
expor that run concurrent refreshes which trip reuse detection. - Wire revocation: on logout, password reset, account disablement and incident response, revoke the refresh token through the RFC 7009 endpoint (
token_type_hint=refresh_token) and confirm the server cascades to access tokens. See the OAuth Token Introspection and Revocation Guide. - Roll back safely: change lifetimes per client rather than globally, keep the previous values recorded, and lengthen the idle window before disabling rotation if users are logged out too often.
Detection and measurement
- Refresh token reuse: a rotated refresh token presented again is a strong theft signal. Alert on it and on the resulting family revocation. See How to detect use of a stolen OAuth refresh token.
- Location and client drift: refresh or API calls for the same grant from a new network, ASN or user agent, and bulk API reads from an integration's grant outside its normal pattern.
- Token in the wrong place: a refresh token presented to an API, a token type mismatch rejected by a resource server, or tokens found by secret scanning in logs or tickets.
- Lifetime drift: new clients registered with long access token lifetimes or refresh tokens without an absolute expiry.
| Metric | What good looks like |
|---|---|
| Median access token lifetime across APIs | 60 minutes or less; 15 minutes or less for high-value APIs |
| Public clients with refresh tokens neither rotated nor sender-constrained | Zero |
| Refresh tokens without an absolute expiry | Zero |
| Active refresh tokens older than policy maximum (including third-party integrations) | Zero, or each with a documented exception |
| Time from incident decision to refresh token revocation for an integration | Minutes to hours |
| Reuse detection events investigated | 100% |
Common mistakes
- Revoking the access token and stopping there: the attacker holding the refresh token mints another. Revoke the grant or refresh token and confirm the cascade.
- Long-lived refresh tokens nobody tracks: in the Gainsight Salesforce breach, an attacker held 285 OAuth tokens for the integration, created between 2017 and 2023, and used the valid ones against customer orgs in November 2025; Gainsight's own root-cause finding was that OAuth tokens were allowed to live too long. Idle and absolute expiry would have killed most of them.
- Long access tokens to avoid building refresh logic: a 24-hour bearer access token spread across every API and log is worse than a 10-minute token plus a well-protected refresh token.
- Refresh tokens in browser storage without rotation or binding: any XSS can read
localStorage. Move to a BFF or, at minimum, rotation with an absolute cap. - Rotation that extends lifetime forever: sliding expiry without an absolute cap gives an active attacker an unlimited session. RFC 10017 forbids extending the original lifetime for browser apps.
- Concurrent refresh races treated as noise: poorly written clients that refresh in parallel trip reuse detection, so teams switch it off. Fix the client (single refresh in flight) or use the server's short grace period, and keep detection on.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| OWASP ASVS 5.0 | V10.4.5, V10.4.8, V10.4.9; V10.1.1; V9.2.2 | Refresh replay mitigation, absolute expiry, user revocation; tokens only to components that need them; token type checks |
| NIST SP 800-53 Rev. 5 | IA-5 Authenticator Management; AC-12 Session Termination; SC-23 Session Authenticity | Token lifetime and protection; ending sessions and grants; binding tokens to the client |
| NIST SP 800-53 Rev. 5 | SC-8 Transmission Confidentiality and Integrity; AU-2 Event Logging | TLS for bearer tokens; logging issuance, refresh and reuse |
| NIST CSF 2.0 | PR.AA-03; PR.AA-05 | Users and services authenticated; access permissions enforced with least privilege |
| MITRE ATT&CK | T1528 Steal Application Access Token; T1550.001 Application Access Token | Theft and replay of both token types |
Practitioner checklist
- Set access token lifetimes per API, 60 minutes or less, 15 minutes or less for high-value APIs.
- Give every refresh token an idle expiry and an absolute expiry that rotation does not extend.
- Rotate or sender-constrain every public client's refresh tokens, with reuse detection alerting.
- Do not issue refresh tokens for the client credentials grant.
- Keep tokens out of the browser with a BFF where you can; never store refresh tokens in
localStoragewithout rotation and a short cap. - Reject refresh tokens and ID tokens at APIs; validate
aud,exp, scope and token type. - Revoke refresh tokens on logout, password reset, disablement and incidents, and test the cascade.
- Inventory refresh tokens held by third-party integrations and expire idle ones.
- Keep both tokens out of URLs, logs and support tickets, and scan for them.
Standards and references
- IETF: RFC 6749 The OAuth 2.0 Authorization Framework
- IETF: RFC 9700 Best Current Practice for OAuth 2.0 Security
- IETF: RFC 10017 OAuth 2.0 for Browser-Based Applications
- IETF: RFC 7009 OAuth 2.0 Token Revocation
- IETF: RFC 6750 OAuth 2.0 Bearer Token Usage
- IETF: RFC 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- OWASP: Application Security Verification Standard 5.0
Related NHI Mgmt Group resources: Token and Session Security Guide · OAuth 2.0 and OpenID Connect Guide · API Keys vs OAuth Tokens Guide · OAuth Token Theft Incident Playbook · Global Token Revocation Guide