ID Token vs Access Token: Differences and How to Validate

Guilliano Molaire Guilliano Molaire 7 min read

In the ID token vs access token question, the short answer is that an ID token tells your application who just signed in, and its audience (the aud claim) is your application’s client ID. An access token tells an API what the caller is allowed to do, and its audience is that API. They are issued together in an OpenID Connect login, they are often both JWTs, and they look similar when decoded, but neither one should ever stand in for the other: the ID token is for the client, and the access token is for the resource server.

This post sets the two side by side, walks through the bugs that appear when they get mixed up (including a Cloud Foundry UAA fix from 2026), and gives a validation checklist for each.

What is the difference between an ID token and an access token?

OAuth 2.0 (RFC 6749) only defines the access token, and it treats that token as something the client carries to an API without needing to read it. OpenID Connect (OIDC) is the identity layer built on top of OAuth 2.0, and it adds the ID token: a JWT, normally signed, that tells the client who authenticated, when, and how.

ID token Access token
Defined by OpenID Connect Core OAuth 2.0 (RFC 6749); JWT profile in RFC 9068
Purpose Proves to the client that a user signed in Lets a client call an API on a user’s or its own behalf
Audience (aud) The client’s own client_id The API (resource server)
Typical claims sub, iss, aud, exp, iat, nonce, auth_time, profile claims sub, iss, aud, exp, scopes or roles, azp or client_id
Who validates it The client application The API receiving the request
Where it goes Kept by the client; sent back to the provider only as id_token_hint at logout Sent in the Authorization: Bearer header to the API
Format Always a JWT A JWT or an opaque string, depending on the server
Lifetime Short; read at login and reissued on refresh Short; renewed with a refresh token

Both can be pasted into a decoder to inspect their claims, and our free JWT token analyzer does that locally in your browser. Decoding a token only shows its contents, so it does not tell you whether the token is valid.

What goes wrong when you mix them up?

Most of the bugs we see come from one of three mistakes.

Sending the ID token to an API. The ID token’s audience is your client, so a correctly built API rejects it. When the API accepts it anyway, it is usually not checking aud at all, so it may accept tokens issued for other applications too. That is also the root of the more general problem of tokens being accepted by the wrong service, covered in why your Keycloak MCP server returns 401 for the wrong audience from the other direction.

Accepting an access token as proof of login. A client that receives a token and treats its presence as “the user is signed in” can be handed a token that was issued for something else entirely. Login should be confirmed from an ID token that you validated for your own client, including the nonce you sent in the request.

Skipping aud, azp and nonce checks. Signature and expiry checks are not enough. The aud check ties the token to the party that should receive it, azp (authorized party) names the client the token was issued to, and nonce ties an ID token to the login request your client started, which prevents replay.

A worked example: Cloud Foundry UAA pull request 4055

Cloud Foundry UAA fixed a bug of the second and third kind in 2026: a login path that accepted an ID token without checking that it had been issued to the right client. According to the commit (written on 11 August 2026 and merged on 27 August through pull request 4055), an identity provider registered with an issuer equal to UAA’s own token endpoint skipped audience validation on the presented ID token, because that check had only been wired up for external OIDC providers. Any JWT signed by that UAA instance, issued to any client for any purpose, was therefore accepted as a valid ID token for that login, which let a token issued in one client context be replayed to authenticate a different external-OIDC-mapped identity.

The fix, first included in UAA v79.7.0, checks the identity provider’s configured relying-party client ID against the token’s aud claim during the interactive browser login callback. Machine-to-machine exchanges such as the JWT bearer grant are deliberately left out, because in those flows the calling client has already authenticated to the token endpoint and is expected to present a token minted for another client. Check Cloud Foundry’s security advisories for any CVE assigned to this fix and for the affected versions.

The same check applies to any system that accepts tokens: an ID token must be checked against the client it was issued to, and an access token against the API that receives it. If you are moving off UAA, our UAA to Keycloak migration guide covers the move.

What is the validation checklist for each token?

Both token types need the common JWT checks: a valid signature against the issuer’s published keys, an unexpired exp, an iss that matches the issuer you expect, and a signing algorithm from an allow-list you chose (never one read from the token’s own header without checking it).

ID token, checked by the client:

  1. aud contains your client_id, and you reject the token if it lists other audiences you do not trust. Check azp only if your provider documents it (Keycloak sets azp to the client that requested the token, so it should equal your client_id).
  2. nonce matches the value your client sent in the authorization request.
  3. iat and exp are reasonable, and auth_time is recent enough if you asked for max_age.
  4. The token arrived in a response to a request your client started (the authorization code exchange), not from an arbitrary request.

Access token, checked by the API:

  1. aud contains this API’s identifier.
  2. The scopes or roles in the token allow the requested action.
  3. Validate locally if the token is a JWT, or ask the server using token introspection if it is opaque. If your server issues RFC 9068 access tokens, also check that the header typ is at+jwt, which an ID token never carries. Keycloak by default marks the token type in a typ claim instead (Bearer for access tokens, ID for ID tokens), and an API can reject anything that is not Bearer.
  4. The caller, shown by azp or client_id, is a client you expect.

The practical details for a Keycloak backend are in verifying a Keycloak access token on the backend, token validation for APIs and introspection versus local validation. For general JWT hygiene, see JWT best practices, and for the sign-in protocol itself, OpenID Connect explained for developers and the OAuth 2.0 visual guide. If you want to check which keys a token should verify against, the JWKS verifier helps.

What about refresh tokens?

A refresh token is a third credential, used by the client to get a new access token without asking the user to sign in again. It is not meant for the application’s own login logic, and it should be stored and transmitted with more care than either of the others. Rotation and reuse detection are covered in our refresh token rotation guide.

How does this look with Skycloak and Keycloak?

Skycloak runs upstream Keycloak, so the tokens it issues are standard OIDC tokens that you can inspect and validate with any OIDC library. By default Keycloak’s Audience Resolve mapper (in the built-in roles client scope) adds as audiences the clients whose client roles the token contains, which is why many default access tokens show aud: "account", and a token with no client roles may have no aud at all. To make a token specific to one API, add a client scope with an Audience mapper whose Included Client Audience is that API, assign it to the calling client, and have the API require its own ID in aud. That keeps access tokens API-specific, so an API can reject any token not issued for it, while the ID token stays scoped to the application that signed the user in.

Frequently asked questions

Can I use the ID token to call my API?

You should not. The ID token is meant for the client application, and its audience is that client. An API should be called with an access token whose audience is the API, so that tokens issued for other purposes are rejected.

Should a single-page app read the access token?

The access token is for the API, so the app generally just attaches it to requests and does not need to read it. An app that needs to know who the user is should read the ID token, or call the userinfo endpoint, instead of decoding the access token, because the access token’s format is the API’s concern and may change. Where you can, keep tokens out of the browser entirely with a backend-for-frontend, as described in our BFF guide.

Is an ID token a JWT?

Yes. OpenID Connect Core requires the ID token to be a JWT. Access tokens may be JWTs (RFC 9068 describes a profile for that) or opaque strings, depending on the server.

Does OAuth 2.0 have ID tokens?

No. OAuth 2.0 defines access tokens and refresh tokens. The ID token was introduced by OpenID Connect, which adds authentication on top of OAuth 2.0.

Sources

  • OpenID Foundation, “OpenID Connect Core 1.0”, ID Token section, https://openid.net/specs/openid-connect-core-1_0.html#IDToken
  • IETF, RFC 6749, “The OAuth 2.0 Authorization Framework”, https://datatracker.ietf.org/doc/html/rfc6749
  • IETF, RFC 9068, “JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens”, https://datatracker.ietf.org/doc/html/rfc9068
  • Cloud Foundry UAA, “Enforce audience check for self-referencing OIDC identity providers” (commit 99d180d) and its follow-up commit c98c67e, merged through pull request 4055, https://github.com/cloudfoundry/uaa/pull/4055

Identity management as a service, on open source

Skycloak does what Auth0 and Okta do, SSO, MFA, SCIM, audit logs and enterprise federation, on an open source core. Unlimited users and applications on every plan, no charge per monthly active user, and you can export and self-host whenever you want.

Guilliano Molaire
Written by
Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Start Free Trial Talk to Sales
© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman