Skip to content
authentication

OpenID Connect (OIDC) Explained: The Modern Identity Layer on OAuth 2.0

Updated 2026-05-15 · 12 min read · By @guptadeepak

Key takeaways

  • OIDC is an authentication layer on top of OAuth 2.0; the ID Token is its core artifact, separate from the access token.
  • Discovery (.well-known/openid-configuration) is the feature that makes OIDC dramatically easier to integrate than SAML.
  • The ID Token is a JWT and must be validated on every login: signature, issuer, audience, exp, nonce, and (for code flow) the c_hash and at_hash.
  • Authorization Code with PKCE is the only flow new code should use. Implicit and Hybrid are legacy.
  • OIDC's scopes (openid, profile, email, offline_access) and standard claims interoperate across providers; custom claims do not.
  • OIDC for enterprise SSO behaves like SAML did in 2010 — pick a CIAM that ships per-Organization OIDC connections so each customer's IdP is configurable, not coded.

What OIDC actually is

OIDC is published by the OpenID Foundation, not the IETF. The Core specification (Sakimura, Bradley, Jones, de Medeiros, Mortimore, 2014) plus Discovery, Dynamic Client Registration, and the Session Management / Logout specs make up the working set. The protocol is layered: OAuth 2.0 is the substrate, the JWT spec is the token format, JOSE handles the signing and encryption, and OIDC is the identity contract on top.

Two parties:

  • Relying Party (RP): the application that wants to know who the user is. Your application, in OIDC terms.
  • OpenID Provider (OP): the IdP. Google, Microsoft Entra, Okta, Auth0, Keycloak, Ping — same set as SAML, different protocol.

Where SAML has the SP send an XML AuthnRequest and the IdP return an XML Response with an Assertion, OIDC has the RP send an OAuth 2.0 authorization request and the OP return an authorization code (then, after the code/token exchange, an ID Token and an access token).

Discovery: the feature that made OIDC easy

Every OIDC provider publishes a JSON document at https://<issuer>/.well-known/openid-configuration describing its endpoints, supported algorithms, supported scopes, supported claims, supported response types, and the JWKS URL where its signing keys live. A new integration that would take an afternoon of SAML metadata juggling is, in OIDC, paste-the-issuer-URL and the library auto-configures.

The discovery document is what makes self-service IdP setup actually self-service: the customer's IT admin enters their issuer URL plus a client_id/client_secret pair, and your SaaS does the rest. SAML's metadata XML exchange achieves the same goal but requires either uploading an XML file or fetching it from a URL the customer has to host.

Signing key rotation also gets easier: the JWKS URL is in the discovery document; your client fetches it on demand (caching with the documented TTL) and the OP rotates keys without coordinating.

The flows: which to use

OIDC inherits OAuth 2.0's flows, with identity claims added. The flows that matter:

  • Authorization Code Flow with PKCE: the only flow new code should use. The RP redirects the user to the OP, the OP authenticates the user, the OP redirects back with an authorization code. The RP exchanges the code for an ID Token, an access token, and (if requested) a refresh token at the token endpoint. PKCE binds the request to the redemption so an intercepted code is useless.
  • Implicit Flow: deprecated. Returned the ID Token directly in the URL fragment. Replaced by Authorization Code + PKCE.
  • Hybrid Flow: returns some tokens in the front channel (URL fragment) and some via code exchange. Rare in practice; Authorization Code is cleaner.
  • Client Credentials Flow: OAuth 2.0 flow for service-to-service. Not really OIDC — no user identity is involved — but commonly mixed in when an OIDC provider also serves machine-to-machine clients.
  • Device Authorization Flow: for input-constrained devices (TVs, CLI tools). User authorizes on a second device; the device polls the token endpoint until authorization completes.

The ID Token: what's in it, what to validate

The ID Token is a JWT signed by the OP. The required claims:

  • iss — issuer URL. Must equal the configured OP for this connection.
  • sub — subject identifier. Stable per-user-per-OP. The thing to key your user records on, not email.
  • aud — audience. Must contain your client_id.
  • exp — expiration time. Must be in the future.
  • iat — issued-at time.
  • nonce — the value your client sent on the authorization request. Must match what you remembered for this request.

Plus, when requested via scopes:

  • openid (required to get any ID Token at all) — gives sub.
  • profile — name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at.
  • email — email, email_verified.
  • address, phone — the corresponding profile fields.
  • offline_access — request a refresh token along with the access token.

Validation, in order, on every login:

  1. Signature: verify with the JWKS key referenced by the kid header. Reject alg: none and reject any algorithm not in the OP's id_token_signing_alg_values_supported from discovery.
  2. iss: equals the OP's expected issuer URL exactly (no http-vs-https confusion, no trailing slash drift).
  3. aud: contains your client_id; if there are additional audiences, your client_id is also in azp.
  4. exp: in the future. Recommend small leeway (a few seconds) for clock skew.
  5. iat: not absurdly old. Recommend rejecting tokens older than a few minutes.
  6. nonce: matches the value bound to this login attempt's session.
  7. c_hash / at_hash (when present): the truncated SHA-256 of the authorization code / access token. Defends against substitution of the code or access token between the redirect and the token exchange.

The pattern of OIDC vulnerabilities: skipping one of these checks. The alg: none attack against pre-2015 JWT libraries was the canonical example. Every library now defaults to safe behavior; the bugs that ship are usually in custom code that re-implements validation.

ID Token vs access token: the recurring confusion

The split is structural and important:

  • ID Token: tells the RP who the user is. Audience is the RP. Consumed by the RP's session-creation code. Never sent to APIs.
  • Access Token: authorizes API calls. Audience is the resource server (your API or a downstream service). Format is OP-specific — sometimes a JWT, sometimes opaque. Never used by the RP to identify the user (use the ID Token for that).

What goes wrong: an SPA gets the ID Token, then sends it as the bearer token to a backend API. The backend API's JWT validator either rejects it (audience mismatch, correctly) or accepts it (audience configured wrong — the bug). If accepted, the backend is now keying authorization off claims meant for session creation, which is the wrong contract.

Refresh tokens are a third artifact when offline_access is requested. Treat them as confidential, store them in a backend (not the browser), rotate per RFC 6749bis recommendations, and bind them to the client where possible.

Standard scopes and claims interop

OIDC's biggest practical win over OAuth-2-without-OIDC is the standardized scope-to-claims mapping. openid profile email returns the same broad set of claims (sub, name, email, email_verified, etc.) across every OIDC provider. Code that consumes those standard claims is portable across IdPs without changes.

Custom claims (non-standard, OP-specific) do not interop. https://example.com/roles from one OP and groups from another are different things and require per-IdP attribute mapping — exactly the situation SAML lives with.

For B2B SaaS, the right pattern: rely on standard claims for the universal stuff (sub, email, name), allow per-Organization custom claim mapping for groups/roles/tenant data, and document which claims you require so the customer's IdP admin can configure them.

OIDC for enterprise SSO

OIDC is increasingly the default for new B2B SaaS enterprise SSO. The procurement-questionnaire conservatism that kept SAML required is loosening — Okta, Entra, Ping, Google Workspace all ship OIDC as a first-class option, and large enterprises are willing to configure it when asked.

The architectural decision that makes OIDC enterprise SSO scalable is the same as SAML: per-Organization configuration. Each customer's Organization has its own OIDC issuer URL, client_id, client_secret, and claim mappings. The CIAM products that handle per-Org OIDC cleanly: WorkOS, Auth0 (Enterprise Connections), Frontegg, MojoAuth, SSOJet, Scalekit, and for self-hosted, Keycloak and Ory.

The protocol-pick is covered separately in Enterprise SSO: SAML vs OIDC, and How to Pick. The short version: ship both, let the customer choose, do not be the blocker.

Implementation guidance

The bullet list, in roughly the order that things break in production:

  1. Use a maintained library. openid-client (Node), Authlib (Python), IdentityModel.OidcClient (.NET), oauthlib + requests-oauthlib for general OAuth needs. For B2B SaaS, prefer a CIAM that handles the per-Org OIDC story as a product.
  2. Validate every ID Token claim listed above. Especially nonce, iss, and aud. The library should do this by default; if it doesn't, switch libraries.
  3. PKCE on every flow, public and confidential. No exceptions.
  4. Bind sessions to sub, not email. Email can change; sub is the stable identifier.
  5. Keep the ID Token small. Use scopes minimally; pull richer profile data from /userinfo on demand.
  6. Cache JWKS with the documented TTL. Do not re-fetch on every login. Do not cache forever — keys rotate.
  7. Per-Organization OIDC connections from day one. The retrofit from "one OIDC connection for the whole SaaS" to "per-customer OIDC connections" is a meaningful rewrite.
  8. Combine with SCIM for lifecycle. OIDC authenticates and provides claims at login; SCIM handles ongoing user provisioning and deprovisioning at enterprise scale.

Related vendors

FAQ

What's the difference between OAuth 2.0 and OIDC?
OAuth 2.0 is an authorization protocol — it answers 'can this client take this action on this resource'. It deliberately says nothing about user identity. OIDC is the OpenID Foundation's specification that layers identity on top: it adds the ID Token (a JWT containing identity claims), the userinfo endpoint, discovery, and the standard set of scopes and claims. Every OIDC server is also an OAuth 2.0 server; not every OAuth 2.0 server is an OIDC server.
ID token vs access token: when do I use each?
The ID token proves who the user is — pass it back to the OIDC client (your application) for session creation; do not send it to APIs. The access token authorizes API calls — pass it in the Authorization header to your backend or downstream services. Mixing them is one of the most common OIDC bugs: an ID token sent to an API has the wrong audience, and an access token used for session identity has no defined claims structure.
Do I need PKCE for confidential clients?
OAuth 2.1 says yes, mandatory for all clients. OIDC inherits this. Public clients (SPAs, mobile apps) cannot keep a secret, so PKCE replaces the secret-based proof-of-possession. Confidential clients (server-side apps) also benefit because PKCE protects against authorization-code injection at the redirect URI. Modern OIDC libraries enable PKCE by default.
What is the nonce parameter and why does it matter?
The nonce is a client-generated random value sent on the authorization request and echoed back in the ID Token. The client verifies the echoed nonce matches what it sent; this binds the ID Token to the specific login attempt and defends against token replay. Omitting nonce verification is the OIDC equivalent of the SAML missing-InResponseTo bug — the ID Token validates, but it might be one the attacker captured from another session.
When should I use the userinfo endpoint vs claims in the ID Token?
ID Token for the small set of claims you need at session creation (sub, email, name). Userinfo endpoint for richer profile data you fetch on demand. Stuffing every possible claim into the ID Token bloats every request that carries it; the userinfo split is what keeps OIDC interactions performant at scale.
Can I use OIDC for B2B enterprise SSO?
Yes, and increasingly this is the preferred protocol for new integrations. The pattern is per-Organization OIDC connections: each customer's Organization in your B2B SaaS has its own OIDC issuer URL, client_id, client_secret, and claim mappings. CIAM products that ship this as a primitive: WorkOS, Auth0 Enterprise Connections, Frontegg, Keycloak, MojoAuth. Without per-Org configuration, OIDC enterprise SSO does not scale past the first few customers.

Sources

  • OpenID Connect Core 1.0 (Sakimura, Bradley, Jones, de Medeiros, Mortimore)
  • OpenID Connect Discovery 1.0
  • OpenID Connect Dynamic Client Registration 1.0
  • RFC 7519 — JSON Web Token (JWT)
  • RFC 8252 — OAuth 2.0 for Native Apps
  • OAuth 2.0 Security Best Current Practice (draft-ietf-oauth-security-topics)
Last reviewed 2026-05-15.