You finish an OAuth/OIDC login, you’re holding an access token, and you need the user’s email and name. The token is right there — often a JWT you can decode in one line. So you decode it and read the claims out.
That one shortcut is the most common identity bug in OAuth systems, and it’s worth understanding why — because the code usually works, right up until it doesn’t.
Two different documents for two different readers
OAuth 2.0 and OpenID Connect answer different questions:
- Authorization — is this client allowed to do X? → the access token.
- Authentication — who is this user? → the ID token (and
/userinfo).
An access token is a key to a room. An ID token is an ID card. Both can be in your pocket; they are not interchangeable. You don’t prove who you are by showing a hotel key — even though the key got you into the room.
Why the access token is the wrong source — even when you can read it
The tempting rebuttal: “but my provider issues a JWT access token, and it has
an email claim.” Both PingFederate and Auth0 can do that. It still doesn’t make
it a valid identity source:
- It’s opaque by contract. The OAuth spec deliberately leaves the access token’s format to the authorization server. It can be a reference string today and an encrypted JWT tomorrow — and nothing broke a promise, because no promise was ever made to you. Code that parses it is built on sand.
- Wrong audience. The access token’s
audis your resource API, not your client app. Reading it for login is reading a document addressed to someone else — the classic “confused deputy” setup. - Wrong purpose and lifetime. It’s tuned to authorize API calls: short, scoped, revocable on the resource server’s terms. It is not a considered statement about identity for your session.
- No
nonce, no client-audience check. The validation ceremony that makes an ID token trustworthy for your app was never run on the access token.
What actually proves identity
- The ID token — a JWT of identity claims, minted for your client, which
you validate (signature,
iss,aud= your client,exp,nonce). After that, itssub,email,nameare trustworthy. - The
/userinfoendpoint — call it with the access token (Authorization: Bearer …) when you want fresh or fuller claims. Note the roles reverse: the access token is the key that lets you ask, and the response is the identity answer. That’s the access token doing its real job.
The rule, and the identifier
The access token gets you into the API. It never tells you who knocked. Identity comes from the ID token or
/userinfo— never from decoding the access token.
And once you have identity: key users on sub + iss, not email. sub is
the stable, opaque, per-issuer identifier. Email changes, and a released address
can be handed to a different person — key on it and you’ve built an
account-takeover path.
The full flow this lives inside: the OIDC authorization-code flow with PKCE.