OAuth is authorization, OIDC is authentication
OAuth 2.0 answers one question: may this application act on this user’s behalf? It hands out access tokens for APIs. It deliberately says nothing about who the user is. That gap caused years of confusion, and OpenID Connect is the fix.
OpenID Connect is a small identity layer on top of OAuth 2.0. It standardises the login flow, defines an ID token that asserts identity, and publishes metadata so clients can be configured without guesswork. When you click “Sign in with Google” or an enterprise SSO button, you are using OIDC.
The practical rule: if you need to know who the user is, use OIDC. If you need an app to do something on their behalf, use OAuth. Most logins need both, which is why the two are usually deployed together.
The ID token
The centrepiece of OIDC is the ID token: a JWT signed by the identity provider. It carries the claims your application needs to establish a login.
sub— the stable, unique identifier for the user.iss— the issuer, so you know which provider signed it.aud— the client id it was issued for; a token for another app must be rejected.expandiat— when it expires and when it was issued.nonce— echoes the value you sent, to bind the token to this login attempt.email,name,picture— profile claims, when the requested scopes allow them.
The ID token is for your client. It is not the credential you send to downstream APIs — that is the access token’s job. Keeping the two distinct avoids a common design mistake.
Discovery
Every compliant provider publishes a discovery document:
https://accounts.example.com/.well-known/openid-configuration
It lists the authorization, token, userinfo and JWKS endpoints, plus supported scopes and algorithms. Clients fetch it at startup and configure themselves, so nothing is hard-coded and provider changes are picked up automatically.
The authorization code flow with PKCE
The recommended flow keeps credentials out of the browser. The browser only ever handles a short-lived code.
- The client generates a random
code_verifier, hashes it into acode_challenge, and redirects the browser to the provider withscope=openid. - The user authenticates at the provider — the only place a password or second factor is entered.
- The provider redirects back with an authorization
code. - The backend exchanges the code, plus the verifier, for an ID token and access token.
- The client validates the ID token and creates a session.
PKCE (Proof Key for Code Exchange) binds the code to the client that requested it. A code intercepted in transit is useless without the verifier. Always send state to prevent CSRF and nonce to bind the ID token to the request.
Scopes and claims
Scopes decide which claims you may receive.
openid— required; without it the request is plain OAuth, not OIDC.profile— name, picture and other profile fields.email— the user’s email and whether it is verified.offline_access— requests a refresh token so the app can act later.
Ask for the minimum you need. Each extra scope is more data to protect and, for some providers, more consent friction for the user.
The userinfo endpoint
The ID token often carries only the basics. For more profile data, call the userinfo endpoint with the access token:
const userinfo = await client.userinfo(tokenSet.access_token);
// { sub, name, email, email_verified, picture, ... }
Treat the sub from userinfo as authoritative and make sure it matches the sub in the ID token. Never trust an email as a stable identifier — people change them.
Validating the ID token
Validation is the step that makes the whole thing safe, and the step most often done wrong. Decoding the payload is not verification; anyone can craft a token with any claims.
Before trusting a claim:
- Signature — verify it with the provider’s public key from the JWKS endpoint, using an allowed algorithm.
- Issuer —
issmust equal the expected provider. - Audience —
audmust be your client id. - Expiry —
expmust be in the future, with a little clock skew. - Nonce — must match the value you sent for this login.
Use a maintained library such as openid-client and let it do all five. Do not hand-roll JWT parsing for authentication.
Sessions after login
OIDC authenticates once, at login. It does not manage your application’s session. After validating the ID token, create your own session — a cookie or a token — keyed on sub.
Keep three things straight:
- The ID token is for your client and proves identity.
- The access token is for calling APIs.
- Your session is how your app remembers the user across requests.
Mixing them leads to leaking provider tokens to the browser or using an ID token as an API credential, both of which are mistakes.
Logout
Local logout comes first: clear your session so the user is signed out of your app. That is always under your control and always reliable.
Federated logout is best-effort. You can redirect to the provider’s end-session endpoint with an id_token_hint, but not every provider honours it and the redirect may not complete. Design so that clearing your own session is sufficient, and never rely on the provider to sign the user out.
When to use OIDC
OIDC is the right choice when:
- You want to avoid storing passwords at all.
- You need enterprise SSO or “Sign in with…” buttons.
- You are one of several apps that should share a login.
- You want MFA and account recovery handled by a specialist.
It is heavier than a simple password form. For a small internal tool with a handful of users, Session Auth and Password Hashing may be simpler and entirely sufficient.
Best practices
- Always use the authorization code flow with PKCE.
- Send and verify both
stateandnonce. - Validate the ID token fully — signature, issuer, audience, expiry.
- Use discovery instead of hard-coded endpoints.
- Request the minimum scopes.
- Keep your session separate from provider tokens.
- Store client secrets on the backend, never in the browser.
Common mistakes
- Treating OAuth as authentication and reading identity from an access token.
- Decoding the ID token without verifying its signature.
- Skipping the
audorisschecks, so a token from another app is accepted. - Using the implicit flow and exposing tokens in the URL.
- Trusting the email address as the user’s primary key.
- Sending provider tokens to your own APIs as if they were session credentials.
- Forgetting that logout must clear your session first.
Where to go next
If you have not read it yet, start with OAuth 2.0 to understand the authorization protocol OIDC extends, and the JWT guide for the token format. Once login succeeds, the Session Auth guide shows how to keep the user signed in, and RBAC & Permissions covers what they are allowed to do.