ID token vs access token vs refresh token
OpenID Connect flows hand a client up to three different tokens, and most JWT confusion comes from treating them interchangeably. They answer different questions for different audiences:
| ID token | Access token | Refresh token | |
|---|---|---|---|
| Answers | "Who signed in?" | "What may this caller do?" | "May I get new tokens?" |
| Consumed by | The client app | The API (resource server) | The authorization server only |
| Format | Always a JWT | JWT (RFC 9068) or opaque | Usually opaque |
| Typical lifetime | Minutes | Minutes to an hour | Days to months, often rotated |
ID token — proof of authentication
The ID token exists for the client: it proves a user authenticated, and carries identity claims like sub, email, and auth_time. Its aud is the client's own ID (with azp disambiguating when there are several), and the client must check nonce to bind it to the login request it started.
Access token — authorization for an API
The access token is for the API. The client should treat it as an opaque string to attach to requests — even when it happens to be a JWT. When it is a JWT, RFC 9068 profiles it: scope lists granted permissions, client_id names the caller, and the API must verify the signature, iss, its own aud, and exp on every request.
Refresh token — long-lived credential
The refresh token is presented only to the authorization server, to obtain fresh ID/access tokens without re-prompting the user. It's the most dangerous of the three if stolen, which is why modern guidance rotates it on every use and revokes the family on reuse detection. It should never be sent to an API and never needs to be a JWT.
The classic mistakes
- Calling an API with the ID token. Its audience is the client, so a correct API rejects it — and an incorrect API that accepts it has an audience-check bug.
- Reading claims from the access token in the client. Its format is a contract with the API, not with you; providers change it without notice.
- Skipping
noncevalidation on the ID token, which reopens replay attacks the claim exists to stop.
Not sure which token you're holding? Paste it into the TokenPrism debugger — the annotated claims (aud, scope, nonce, azp) identify it immediately, or diff two tokens to see how your provider's ID and access tokens differ.