An ID token tells your app who signed in. An access token lets your app call an API. A refresh token lets your app get new access tokens from the authorization server. Each has a different audience, so sending one where another belongs is a common source of bugs and holes.
The three tokens side by side
| ID token | Access token | Refresh token | |
|---|---|---|---|
| Defined by | OpenID Connect | OAuth 2.0 | OAuth 2.0 |
| Meant for | The client app | The resource server (API) | The authorization server |
| Answers | Who authenticated? | What may this client do? | Can I get a new access token? |
| Format | Always a JWT | Often opaque, sometimes a JWT | Usually opaque |
| Sent to an API? | No | Yes | Never |
ID token
The OpenID Connect Core spec defines the ID token as a security token with claims about the authentication of an end user by an authorization server. It is a JWT. Your app checks the signature, issuer, audience and expiry, then uses the sub claim as the stable user identifier.
The ID token is addressed to your client. Its audience must include your client ID. It is not meant to be forwarded to an API as proof of permission.
Access token
RFC 6749 describes access tokens as credentials used to access protected resources, representing specific scopes and durations of access. The string is usually opaque to the client. The client does not need to read it. The API either looks it up or, if it is a JWT, validates it locally.
Because an access token is a bearer credential, anyone holding it can use it until it expires. That is why short lifetimes and narrow scopes matter. RFC 9700 says the privileges of an access token should be restricted to the minimum needed.
Refresh token
Refresh tokens are credentials used to obtain new access tokens when the current one expires, or to obtain additional access tokens with the same or narrower scope. Issuing one is optional and left to the authorization server.
Treat it as the most sensitive of the three, because it outlives the access token. RFC 9700 says refresh tokens for public clients must be sender constrained or use refresh token rotation. For confidential clients, they can be used only by the client they were issued to.
Common mistakes
- Using the ID token as an API credential. The API was not its audience.
- Using an access token as proof of login. It was issued to call an API, and its format may be opaque to your app.
- Logging tokens. All three are secrets while valid.
- Putting a refresh token where scripts can read it, such as browser storage, without a plan for theft.
- Skipping audience checks on the API. A JWT access token is valid for the resource it names, not every API that trusts the same issuer.
A quick test for code review
For every place a token is read, ask who the intended recipient is. If the answer is not the code that receives it, there is a bug. A short list in your design notes, naming which token goes to which component, catches most mix ups before they ship.
Quick check
Can I decode an access token in my front end to read the user's name?
You should not rely on it. Access tokens are meant for the API and may be opaque. Use the ID token or the UserInfo endpoint for profile data.
Do I always get a refresh token?
No. RFC 6749 says issuing a refresh token is optional and at the discretion of the authorization server.
Safeguard's SBOM and dependency scanning shows which auth libraries you ship and which have known advisories. A free scan is available.
Sources
- OpenID Connect Core 1.0
- RFC 6749, The OAuth 2.0 Authorization Framework
- RFC 9700, Best Current Practice for OAuth 2.0 Security
Last reviewed: 19 October 2026.