OAuth2 Tokens Explained
OAuth2 relies on tokens to grant access to resources securely. But not all tokens serve the same purpose. Understanding Access Tokens, Refresh Tokens, and ID Tokens is key to implementing authentication correctly.
The Three Types of Tokens
OAuth2 defines Access Tokens and Refresh Tokens, while ID Tokens come from OpenID Connect (OIDC), an authentication layer on top of OAuth2.
Each serves a different purpose:
- Access Token → Used to authorize API requests
- Refresh Token → Used to obtain new Access Tokens
- ID Token → Represents user identity (OIDC only)
Access Tokens
Access Tokens are short-lived and sent with API requests to grant access to protected resources. They can be opaque (random strings validated by the authorization server) or JWTs (self-contained tokens with encoded claims).
{
"iss": "https://idp.example.com",
"sub": "user-123",
"aud": "https://api.example.com",
"exp": 1714759200,
"scope": "read:contacts"
}
APIs must always validate the Access Token before accepting a request. If a JWT is used, it should be verified against the authorization server’s public keys.
Refresh Tokens
Refresh Tokens are long-lived and used to obtain new Access Tokens when they expire. They should never be exposed to the frontend because they allow attackers to generate new tokens indefinitely if stolen.
Instead, they should be stored securely on the server, often in a session.
POST /token
Host: idp.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
client_id=YOUR_CLIENT_ID&
refresh_token=YOUR_REFRESH_TOKEN
ID Tokens (OpenID Connect)
ID Tokens are not part of OAuth2 but come from OpenID Connect. They contain identity information about the user and are always JWTs.
Unlike Access Tokens, ID Tokens should not be used to authorize API requests. They are only meant for authentication.
{
"iss": "https://idp.example.com",
"sub": "user-123",
"email": "user@example.com",
"name": "John Doe",
"exp": 1714759200
}
Token Storage Strategies
How tokens are stored depends on the application architecture:
- Backend-for-Frontend (BFF) → Store Refresh Tokens securely in a backend session.
- SPAs & Mobile Apps → Store Access Tokens in memory, avoid storing Refresh Tokens.
- Server-to-Server APIs → Use only Access Tokens (no need for Refresh Tokens).
Security Considerations
Access Tokens should never be stored in localStorage or sessionStorage due to XSS risks. The safest approach is to use HTTP-only cookies.
If the app is a purely client-side SPA, the best approach is:
- Store the Access Token in memory
- Use short-lived Access Tokens
- Refresh the token frequently
Why Refresh Tokens Are Risky in the Frontend
If a Refresh Token is stolen, an attacker can continuously generate new Access Tokens. This is why they should always be stored on the server.
Backend-for-Frontend (BFF) architecture solves this problem by keeping Refresh Tokens in a secure session and issuing short-lived Access Tokens to the frontend.
If you can’t use a BFF, a good alternative is making sure the frontend and API share the same domain so tokens can be stored in HTTP-only cookies managed by the API. This keeps them secure while avoiding client-side storage risks.
ID Tokens vs. Access Tokens
An API should never accept an ID Token as an Access Token.
- ID Token → Identifies the user (who they are)
- Access Token → Authorizes actions (what they can do)
Best Practices
To ensure a secure OAuth2 implementation:
✅ Use short-lived Access Tokens
✅ Store Refresh Tokens only on the backend
✅ Use PKCE for SPAs & mobile apps
✅ Rotate Refresh Tokens to limit risks
✅ Never use an ID Token to authorize API calls
Conclusion
Understanding the differences between Access, Refresh, and ID Tokens is essential for designing secure authentication and authorization flows.
If you’re building an OAuth2-based app, knowing where to store tokens and how to use them properly makes all the difference.
I'm currently writing a book called React Router OAuth2 Handbook, focused on implementing secure OAuth2 authentication in Remix and React Router apps—using patterns you can apply to any web app.
The landing page is live (book coming soon) at books.sergiodxa.com