How OpenID Connect adds identity on top of OAuth.
A small protocol that does one large thing: tells you who the user is. OIDC is the missing identity half of OAuth — a signed JWT, a key endpoint, six verification rules. Everything else is shape.
OAuth vs OIDC: authorization vs authentication
OAuth says what you can do; OIDC says who you are.
OpenID Connect (OIDC) is an identity layer on top of OAuth 2.0, standardised in 2014. While OAuth answers "can this app access this resource?", OIDC answers "who is this user?" by adding an ID token (a JWT containing claims about the authenticated user). Every modern "Sign in with Google / Apple / Microsoft" flow uses OIDC.
OAuth 2.0 issues an access token that says: "the holder may read contacts." It says nothing about who the holder is. If you treat receiving an access token as proof of identity, you have built an open redirect. Anyone with any valid access token from your auth server could trade it for an account on your site.
OpenID Connect is a thin layer on top of OAuth 2.0 that fixes this. Request the openid scope and the auth server returns an additional token — the ID token — a signed JWT whose audience is your client and whose claims describe the authenticated user. OAuth + OIDC = authorization + authentication, in one round trip.
The OAuth guide walks the flow that delivers all this. This page focuses on the OIDC half: the ID token, the verification, and the small surface that makes identity work.
The OIDC ID token: header, payload, signature
The ID token, in three parts.
An ID token is a JWT — three base64url segments, separated by dots: header.payload.signature. Header says how it was signed and which key. Payload carries the claims about the user. Signature proves the auth server actually issued it.
The signature is what makes the whole thing work. Without it, the JWT is just JSON the client could have written itself. With it, the client can trust every claim — provided the signature was made by a key that belongs to the trusted auth server. That key check is what the next part walks through.
Inspecting an OIDC ID token
Decode the claims in your browser, nothing sent over the network.
Below: a real-shaped ID token. Edit it (or paste your own — nothing is sent over the network) and watch the header, payload, and signature decode in place.
{
"alg": "RS256",
"typ": "JWT",
"kid": "abc123"
}{
"iss": "https://accounts.google.com",
"aud": "864998.apps.example",
"sub": "108972536452938478630",
"email": "ada@example.com",
"email_verified": true,
"name": "Ada Lovelace",
"given_name": "Ada",
"family_name": "Lovelace",
"picture": "https://lh3.googleusercontent.com/....",
"locale": "en",
"iat": 1761408000,
"exp": 1761411600,
"nonce": "n-0S6_WzA2Mj"
}| iss | https://accounts.google.com | Issuer — must match the auth server you trust. |
| aud | 864998.apps.example | Audience — must match your client_id. |
| sub | 108972536452938478630 | Subject — opaque, stable user ID. NEVER an email. |
| iat | 2025-10-25 16:00:00 UTC (-5582h) | Issued at (unix seconds). |
| exp | 2025-10-25 17:00:00 UTC (-5581h) | Expires at (unix seconds). Must not be in the past. |
| nonce | n-0S6_WzA2Mj | Replay-defence value you sent in /authorize. |
JWKS: where the public keys live
How the client finds the key that signed a token.
The auth server signs every ID token with its private key, transmitted in turn over an HTTPS channel. To verify, the client needs the matching public key. JWKS (JSON Web Key Set) is how it gets it: a JSON document at a well-known URL that lists every active public key, each tagged with a key ID (kid).
The verification dance: read the kid from the JWT header, find the matching key in the JWKS, verify the signature with it. Auth servers rotate keys regularly — a new kid appears, the old one keeps working for a grace period, then is removed. Cache the JWKS aggressively (with a short TTL or cache-bust on unknown kid) — never fetch it on every request.
Reasonable JWKS strategy: cache for 1 hour. On rotation, the auth server starts signing with a new kid your cache doesn't know about. That is the trigger to refetch — not a periodic clock. This way rotation is detected within seconds rather than within a TTL.
The OIDC discovery endpoint
One well-known URL that lists every endpoint and key.
Every OIDC-compliant auth server publishes a JSON document at /.well-known/openid-configuration describing every endpoint it offers. Authorization endpoint, token endpoint, JWKS URI, supported scopes, supported response types, supported signing algorithms — all there.
This is what makes "Sign in with Google" libraries portable. The library fetches https://accounts.google.com/.well-known/openid-configuration once at startup, resolved through DNS like any other request, and learns where everything is. To switch to Microsoft Entra, change one URL — the library reconfigures itself from the new discovery document.
# typical discovery document (abbreviated)
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": ["code", "id_token", "code id_token", ...],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "email", "profile"],
"code_challenge_methods_supported": ["S256"]
}Mandatory ID token verifications
Six checks to run before you trust any claim.
Receiving an ID token is not the same as trusting it. Six checks must succeed before you treat any claim as fact. Skip any one and you have a vulnerability.
- 01 · sig
Signature
Verify the JWT signature with the public key from JWKS keyed by the kid in the header. If the signature does not verify, stop — the token is forged or corrupted.
- 02 · iss
Issuer match
iss must equal the issuer URL of the auth server you trust. An attacker's forged token signed by a different issuer's key matters only if you accept multiple issuers without strict matching.
- 03 · aud
Audience match
aud must equal your client_id. If the auth server issued an ID token to a different client, refusing it here prevents one client from accepting another's tokens — the same audience-binding rule used by API rate limiters when scoping quota.
- 04 · exp
Expiry
exp must be in the future. Allow a small leeway (typically 60 seconds) for clock skew. Reject tokens older than a couple minutes — ID tokens are not access tokens, they should be short-lived.
- 05 · nonce
Nonce match
nonce must equal the value you sent in the authorize URL. This defends against ID-token replay — an attacker can't reuse a token they intercepted because the new login attempt expects a different nonce.
- 06 · alg
Algorithm allow-list
The alg in the header must be in your hardcoded allow-list (RS256, ES256). Never trust the JWT's self-declared algorithm — the famous alg=none bug let attackers strip the signature entirely.
The three OIDC flows and where they deliver the ID token
Three flows, three deliveries.
OIDC defines three flows that differ in where the ID token gets handed to your client. The Authorization Code flow is the modern default; the others exist for legacy and edge cases.
The modern default
ID token comes back from the token endpoint over the back channel — never through the browser. Pair with PKCE. This is what every modern integration uses.
Both channels at once
ID token returned in the redirect URL fragment AND another at the token endpoint. Lets the client validate identity before completing the code exchange. Niche.
Don't use the implicit flow
ID token returned only in the URL fragment. No code, no token endpoint. Deprecated by OAuth 2.1 alongside the OAuth implicit flow. If you see it in old docs, replace.
Where OIDC trips integrators
Three mistakes that turn into account-takeover bugs.
Use sub, not email.
sub is opaque and stable for the user's lifetime at that issuer. email can change. Linking an internal user to email means an account takeover happens the moment the user updates it.
Never trust JWT alg.
Famously, the JWT spec lets alg=none mean "no signature." Libraries used to honor it. Always verify against your own allow-list of algorithms — never the one the token claims.
Use the ID token.
"They got an access token from Google, so they must be a Google user." This is the trap OIDC exists to fix — the access token has no audience constraint and any token signed by Google works. Use the ID token for identity, the access token for API calls.
OIDC providers in production: Google, Apple, Okta, Auth0, Microsoft
The five identities behind every Sign in with X button.
- Google Identity
- Free for any consumer app. Discovery URL: accounts.google.com/.well-known/openid-configuration. Issues 1-hour ID tokens; refresh tokens last until revoked. The most common social-login provider on the web.
- Sign in with Apple
- OIDC with extra constraints — Apple gates whether email is real or a relay (@privaterelay.appleid.com). Required by App Store rules for any app that offers third-party social login. Strict around the aud claim.
- Microsoft Entra ID (formerly Azure AD)
- The dominant enterprise IDP. Multi-tenant via the /common endpoint or single-tenant via tenant ID. Used by virtually every enterprise SaaS app (Salesforce, Slack, Box, Dropbox).
- Auth0 / Okta
- OIDC-as-a-service. Auth0 is now part of Okta (acquired 2021). Both provide hosted login UIs, social-login federation, and tenant management. Pricing kicks in at modest user counts; common to outgrow at scale.
- Keycloak · Red Hat
- The most popular open-source OIDC provider. Self-hosted; full SAML and OIDC support; free as in beer. Used by many EU government services and self-hosting enterprises.
- Ory Hydra / Kratos
- Newer Go-based OIDC stack, designed for cloud-native deployment. Splits identity (Kratos) from OIDC (Hydra). Smaller footprint than Keycloak; growing in startup adoption.
Build vs buy. Identity is one of the strongest "buy" cases in software. Google's federated login costs zero and handles authentication-grade security (TOTP, WebAuthn, suspicious-login detection) you don't want to build. Self-hosted OIDC makes sense only when compliance requires it or you have multi-tenant identity as a product feature.
OIDC is small, on purpose. One additional scope, one additional token, one well-known discovery URL, one signed JSON document. The verification is six bullet points. Master that surface and you can ship "Sign in with X" against any compliant provider in an afternoon — and unship the half-correct hand-rolled login that came before.
Read
further.
- OpenID FoundationOpenID Connect Core 1.0The spec. Long but readable. The "ID Token Validation" section is exactly Part 06 expanded.
- OpenID FoundationOpenID Connect Discovery 1.0The discovery document defined formally — what every key means and which are required.
- IETF · RFC 7517JSON Web Key (JWK)The format used in JWKS. Short — read it before writing JWKS-handling code.
- Semicolony guideOAuth 2, the flowsThe Authorization Code flow with PKCE that delivers the ID token in the first place.
- Semicolony toolJWT EncoderInspect, decode, sign, and verify JWTs locally. Handy when integrating against a real OIDC provider.