How OpenID Connect (OIDC) Works: Identity on Top of OAuth 2.0
OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0 that lets applications verify a user's identity and obtain basic profile information. Every time you click "Sign in with Google" or "Continue with Microsoft" and the application learns your name, email, and profile picture, OpenID Connect is the protocol making that possible. While OAuth 2.0 handles authorization (granting an app access to your resources), OIDC adds authentication (proving who you are) by introducing a standardized identity token — the ID Token — that OAuth 2.0 deliberately left unspecified.
OIDC is defined by the OpenID Connect Core 1.0 specification (2014) and has become the dominant authentication protocol for both consumer and enterprise applications, largely replacing SAML in new deployments due to its simplicity and native mobile support.
Why OAuth 2.0 Was Not Enough
OAuth 2.0 was designed for authorization — granting a third-party application access to your resources (photos, repos, calendar) without sharing your password. But it deliberately did not define a way for the application to learn who you are. An OAuth 2.0 access token says "the bearer of this token may read the user's photos" — it does not say "the user is [email protected]."
Before OIDC, applications that wanted authentication via OAuth 2.0 would use the access token to call a provider-specific API endpoint (like GitHub's /user or Facebook's /me). Each provider had a different API, returned different fields, and required different request formats. This led to several problems:
- No standard identity format — Every provider returned user info differently. Supporting multiple providers required custom code for each one.
- No verifiable proof of authentication — The application could not prove when or how the user authenticated. The access token just said "this token is valid" — it did not say "the user logged in 5 seconds ago using MFA."
- Extra round trips — The application had to make an additional API call to get user information, adding latency to the login flow.
- Confused deputy — Without a standard audience claim, an access token obtained for one application could be used to impersonate the user at another application that uses the same provider.
OIDC solves all of these by adding a thin, standardized identity layer on top of OAuth 2.0.
The ID Token
The central artifact of OIDC is the ID Token — a JSON Web Token (JWT) that contains claims about the authentication event and the user. Unlike an OAuth 2.0 access token (which is opaque to the client), the ID Token is designed to be parsed and validated by the client application.
A decoded ID Token payload looks like this:
{
"iss": "https://accounts.google.com",
"sub": "110248495921384975384",
"aud": "my-app-client-id.apps.googleusercontent.com",
"exp": 1706461200,
"iat": 1706457600,
"nonce": "abc123def456",
"auth_time": 1706457590,
"at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
"email": "[email protected]",
"email_verified": true,
"name": "Jane Doe",
"picture": "https://lh3.googleusercontent.com/..."
}
The critical claims that every relying party must validate:
iss(issuer) — Must match the expected IdP. If you expect tokens from Google, reject any token with a different issuer.sub(subject) — A unique, stable identifier for the user at this provider. This is the primary key for the user, not their email (which can change).aud(audience) — Must contain your application's client_id. This prevents token confusion attacks where a token issued for one application is used at another.exp(expiration) — The token must be rejected after this timestamp. ID Tokens are typically short-lived (5-60 minutes).iat(issued at) — When the token was created. Can be used to reject tokens that are too old.nonce— A random value that your application generated and included in the authentication request. Must match to prevent replay attacks.
The ID Token is signed using the IdP's private key (typically RS256 or ES256), and the client verifies the signature using the IdP's public key obtained from the JWKS endpoint.
The Authorization Code Flow with PKCE
The recommended OIDC flow for most applications is the Authorization Code Flow with PKCE (Proof Key for Code Exchange, RFC 7636). This is the same OAuth 2.0 Authorization Code flow with two additions: the openid scope (which tells the authorization server to return an ID Token) and PKCE (which protects against authorization code interception).
Step by step:
- PKCE setup — The client generates a random
code_verifier(a 43-128 character string) and computescode_challenge = BASE64URL(SHA256(code_verifier)). The verifier is stored locally; the challenge is sent in the authorization request. - Authorization request — The client redirects the user to the OpenID Provider's authorization endpoint with parameters including
scope=openid email profile,response_type=code,client_id,redirect_uri,state(CSRF protection),nonce(replay protection), andcode_challenge. - User authentication — The OP authenticates the user (login form, MFA) and obtains consent. If the user already has an active session and has previously consented, this step can be instant.
- Authorization code — The OP redirects back to the client's
redirect_uriwith a short-lived authorization code and the originalstatevalue. - Token exchange — The client sends the authorization code along with the
code_verifierto the OP's token endpoint. The OP verifies thatSHA256(code_verifier)matches the originalcode_challenge, confirming that the entity exchanging the code is the same one that initiated the flow. - Tokens received — The OP returns an access token, an ID Token (JWT), and optionally a refresh token.
- ID Token validation — The client validates the ID Token's signature using the OP's JWKS keys, then verifies
iss,aud,exp, andnonceclaims. - UserInfo (optional) — If the client needs additional user attributes not included in the ID Token, it calls the UserInfo endpoint with the access token.
PKCE: Why It Matters
PKCE (pronounced "pixy") was originally designed for public clients (mobile apps, SPAs) that cannot securely store a client secret. Without PKCE, an attacker who intercepts the authorization code (via a malicious app on the same device, browser history, or referrer header) could exchange it for tokens.
With PKCE, the authorization code is useless without the code_verifier, which never leaves the client application. The OP verifies that SHA256(code_verifier) == code_challenge before issuing tokens. Since the code_challenge is a one-way hash, an attacker who sees it cannot derive the verifier.
As of OAuth 2.1 (draft), PKCE is required for all clients, including confidential clients with client secrets. The reasoning is defense in depth: even confidential clients benefit from the additional protection against code injection and interception attacks.
Discovery Document
OIDC includes a standardized discovery mechanism. Every OpenID Provider publishes a JSON configuration document at a well-known URL:
https://accounts.google.com/.well-known/openid-configuration
This document contains everything a client needs to interact with the provider:
{
"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",
"scopes_supported": ["openid", "email", "profile"],
"response_types_supported": ["code", "token", "id_token", ...],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"claims_supported": ["sub", "email", "email_verified", "name", ...]
}
This is a major advantage over SAML, where metadata exchange is often a manual process involving XML file uploads. OIDC discovery makes multi-provider support trivial: the client just needs the issuer URL, and it can derive everything else automatically.
Scopes and Claims
OIDC defines standard scopes that map to sets of claims:
openid(required) — Tells the OP to return an ID Token. Without this scope, the request is a plain OAuth 2.0 request.profile— Claims:name,family_name,given_name,middle_name,nickname,preferred_username,profile,picture,website,gender,birthdate,zoneinfo,locale,updated_at.email— Claims:email,email_verified.address— Claim:address(structured JSON with street, city, region, postal code, country).phone— Claims:phone_number,phone_number_verified.
Claims can appear in the ID Token, the UserInfo response, or both. The OP decides which claims to include in the ID Token versus requiring a UserInfo call — Google, for example, includes email and name directly in the ID Token, while others require a separate UserInfo request.
The UserInfo Endpoint
The UserInfo endpoint is an OAuth 2.0 protected resource that returns claims about the authenticated user. The client calls it with the access token:
GET /userinfo HTTP/1.1 Host: openidconnect.googleapis.com Authorization: Bearer ya29.a0AeDClZ...
Response:
{
"sub": "110248495921384975384",
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"picture": "https://lh3.googleusercontent.com/...",
"email": "[email protected]",
"email_verified": true
}
The sub claim in the UserInfo response must match the sub claim in the ID Token. If they differ, the response must be rejected — this prevents token substitution attacks.
Implicit Flow vs. Authorization Code Flow
The OIDC Implicit Flow returns the ID Token directly in the URL fragment (#id_token=...) without an intermediate authorization code. This was originally designed for SPAs (single-page applications) that could not make back-channel requests to the token endpoint.
The Implicit Flow is now considered insecure and is deprecated by the OAuth 2.0 Security Best Current Practice (RFC 9700). Problems include:
- Token exposure — Tokens appear in the URL fragment, which may be logged by browsers, proxies, or browser extensions.
- No client authentication — There is no way to verify which client is receiving the token.
- No refresh tokens — The implicit flow does not support refresh tokens, so the user must re-authenticate when the access token expires.
- Token injection — An attacker can replace the token in the URL fragment before the client reads it.
Modern SPAs should use the Authorization Code Flow with PKCE. The client makes a cross-origin POST to the token endpoint (which CORS enables) and receives tokens in a response body rather than a URL fragment.
Nonce Validation
The nonce parameter is OIDC's primary defense against replay attacks. The flow works like this:
- The client generates a cryptographically random
noncevalue and stores it (in the session, a cookie, or local storage). - The client includes the
noncein the authorization request. - The OP includes the same
noncevalue in the ID Token. - When the client receives the ID Token, it verifies that the
nonceclaim matches the value it stored earlier.
If an attacker captures an ID Token and tries to replay it, the client will reject it because the nonce will not match the current session's expected value. This is conceptually similar to CSRF tokens but applied to the authentication flow.
Nonce validation is required when using the Implicit Flow (because the ID Token passes through the browser) and strongly recommended for the Authorization Code Flow.
Token Validation
Validating an ID Token properly is critical for security. The full validation procedure is:
- Decode the JWT — Split on dots, Base64url-decode header and payload.
- Verify the signature — Fetch the OP's public keys from the JWKS endpoint (cached locally). The JWT header's
kididentifies which key to use. Verify the signature using the corresponding public key. - Validate
iss— Must exactly match the issuer URL from the discovery document. - Validate
aud— Must contain your client_id. Ifaudis an array, also check that anazp(authorized party) claim is present and matches your client_id. - Validate
exp— Current time must be before the expiration. Allow a small clock skew tolerance. - Validate
iat— Reject tokens issued too far in the past. - Validate
nonce— Must match the nonce you sent in the authorization request. - Validate
at_hash(if present) — Compute the hash of the access token and verify it matches. This binds the ID Token to the access token, preventing token substitution.
Libraries like jsonwebtoken (Node.js), PyJWT (Python), and golang-jwt (Go) handle steps 1-2. Steps 3-8 are application logic that you must implement correctly.
Dynamic Client Registration
OIDC defines an optional Dynamic Client Registration protocol (RFC 7591) that allows clients to register programmatically without manual configuration at the OP. The client sends a POST request to the registration endpoint with its metadata:
POST /register HTTP/1.1
Content-Type: application/json
{
"application_type": "web",
"redirect_uris": ["https://myapp.example.com/callback"],
"client_name": "My Application",
"token_endpoint_auth_method": "none"
}
The OP responds with a client_id (and possibly a client_secret). This enables automated onboarding of new applications without administrator intervention, which is useful in large organizations or platform-as-a-service environments.
Session Management
OIDC extends beyond initial authentication to address session lifecycle:
- Session Management — Uses a hidden iframe to detect when the OP session has changed or ended, allowing the client to re-authenticate the user without a full page redirect. The client periodically messages the OP's iframe, which responds with "changed" or "unchanged."
- Front-Channel Logout — The OP renders iframes pointing to each client's logout URL, triggering session cleanup at each relying party. Similar to SAML Single Logout and shares the same fragility problems.
- Back-Channel Logout — The OP sends a server-to-server POST containing a Logout Token (a JWT) to each client's back-channel logout URL. More reliable than front-channel because it does not depend on the user's browser.
- RP-Initiated Logout — The client redirects the user to the OP's logout endpoint to end the OP session. An optional
post_logout_redirect_urisends the user back to the client after logout.
OIDC vs. SAML: When to Use Which
Both OIDC and SAML provide federated SSO, but they target different environments:
- Use OIDC when building new applications, mobile apps, SPAs, or anything that consumes JSON APIs. OIDC is simpler to implement, uses compact JWT tokens, and has excellent library support across all languages.
- Use SAML when integrating with existing enterprise infrastructure that already uses SAML, or when your customers specifically require SAML support. Many enterprise IdPs (ADFS, PingFederate) have stronger SAML support than OIDC.
- Support both when building B2B SaaS. Your smaller customers will want "Sign in with Google" (OIDC), and your enterprise customers will want SAML integration with their corporate IdP.
Security Considerations
Common OIDC implementation pitfalls:
- Not validating the
audclaim — The single most common OIDC vulnerability. If you accept any ID Token from Google without checking thataudmatches your client_id, an attacker can obtain an ID Token for their own application and use it to log in to yours. - Using email instead of
subas the primary identifier — Email addresses can change, be reassigned, and are not guaranteed unique across all providers. Usesub(oriss+subfor multi-provider) as the primary key. - Not verifying
email_verified— An attacker can register at an OP with someone else's email and use it to impersonate them if the client does not checkemail_verified: true. - Accepting tokens from untrusted issuers — Whitelist your expected issuers and reject tokens from any other
iss. - Missing state parameter — The
stateparameter prevents CSRF attacks on the redirect URI. Without it, an attacker can initiate an authorization flow and trick the victim into completing it, linking the attacker's account. - Redirect URI manipulation — Register exact redirect URIs, never use wildcards. An open redirect in the redirect URI allows token theft.
OIDC and Network Infrastructure
Like all web protocols, OIDC depends on the network stack operating correctly:
- DNS resolves OP endpoints. DNS poisoning could redirect the discovery document to a malicious server, causing the client to use attacker-controlled authorization and token endpoints.
- TLS is mandatory for all OIDC endpoints. Tokens travel in URL parameters, POST bodies, and HTTP headers — all of which must be encrypted.
- JWKS key rotation depends on reliable access to the OP's JWKS endpoint. If the endpoint is unreachable, token validation fails and users cannot log in.
- BGP routes traffic between clients, users, and OPs. BGP disruptions affecting a major OP like Google or Microsoft can block authentication for millions of applications simultaneously.
Relevant RFCs and Specifications
- OpenID Connect Core 1.0 — The core specification defining ID Tokens, flows, claims, and the UserInfo endpoint.
- OpenID Connect Discovery 1.0 — Defines the
.well-known/openid-configurationdiscovery mechanism. - OpenID Connect Dynamic Client Registration 1.0 — Programmatic client registration.
- RFC 6749 — OAuth 2.0 Authorization Framework (the foundation OIDC builds on).
- RFC 7636 — Proof Key for Code Exchange (PKCE).
- RFC 7519 — JSON Web Token (JWT).
- RFC 7517 — JSON Web Key (JWK) and JWKS.
- RFC 9700 — OAuth 2.0 Security Best Current Practice.
See It in Action
Major OpenID Providers operate infrastructure routed via BGP. Explore their networks using the god.ad BGP Looking Glass: