OpenID Connect (OIDC) is the interoperable authentication protocol layered on top of OAuth 2.0. It is the machinery behind every "Sign in with Google / Microsoft / Apple" button — a standardised, JSON-and-HTTP way for an application to verify who a user is and pull basic profile information, without ever handling their password. OAuth 2.0 answers "what can this client access?"; OpenID Connect adds the missing question, "who is this user?", and answers it with a signed token any service can verify. This leaf maps the actors, the flow, the ID Token, and which flow to actually use in 2026.
In the words of the spec authors, OpenID Connect is "an interoperable authentication protocol based on the OAuth 2.0 framework" (IETF RFC 6749 and RFC 6750). OAuth 2.0 by itself is an authorization framework — it issues access tokens that let a client call an API on a user's behalf. It deliberately says nothing about who the user is. Teams kept bolting their own identity semantics onto OAuth, every one slightly different and several insecure. OIDC standardises that missing layer.
OIDC reuses OAuth's message flows — the same authorization and token endpoints, the same redirect dance — and adds three things on top:
openid, profile, email, …) and a discovery mechanism so any client can configure itself against any provider.The payoff the spec leads with: it "removes the responsibility of setting, storing, and managing passwords" from your application. The identity provider holds the credentials; your app holds a verifiable token. That is a smaller breach surface and one less thing to get wrong.
SAML is the older XML-based SSO standard — still everywhere in enterprise, but heavier and browser-POST-bound. OAuth 2.0 is authorization only. OpenID Connect is the modern, JSON/JWT, mobile- and SPA-friendly authentication standard that most new builds reach for. If someone says "we do OAuth login," they almost always mean OIDC — or they have re-invented it badly.
The single most common security mistake in this space is using an OAuth access token as proof of identity. It isn't one. This table is the mental model.
| OAuth 2.0 | OpenID Connect | |
|---|---|---|
| Question answered | What can this client access? (authorization) | Who is this user? (authentication) |
| Primary token | Access Token (opaque or JWT, for the API) | ID Token (always a signed JWT, for the client) |
| Token audience | The resource server / API | The relying party (your app) |
| Triggered by | Any scope | The openid scope specifically |
| User identity | Out of scope | The sub claim + UserInfo |
| Standardises | Token issuance & API access | Identity, claims, discovery on top of OAuth |
A request is a plain OAuth request until the client asks for the openid scope. That scope is what tells the provider "also run authentication and return an ID Token." Everything OIDC adds hangs off that one keyword in the scope parameter.
"A piece of software that requests tokens either for authenticating a user or for accessing a resource." Your application — the thing the user is trying to sign in to.
"An entity that has implemented the OpenID Connect and OAuth 2.0 protocols." Google, Microsoft Entra ID, Auth0, Okta, Keycloak. It holds the credentials and issues tokens.
The person authenticating through their browser or mobile app. They prove who they are to the OP — never to your app directly.
The OpenID Foundation's own framing of the protocol, step by step — the canonical authorization-code path most builds use.
The end user navigates to your site in a browser (or opens your mobile app).
They click "sign in." Your app (the RP) redirects the browser to the OP's authorization endpoint.
The redirect carries response_type=code, the client_id, the redirect_uri, the requested scope (including openid), a state value, and a PKCE code_challenge.
The OP collects credentials (password, passkey, MFA…) and obtains the user's consent. Your app never sees the password.
The OP redirects back with an authorization code; the RP exchanges it at the token endpoint for an ID Token and an Access Token.
Using the Access Token, the RP calls the OP's UserInfo endpoint to fetch additional claims.
The UserInfo endpoint returns the user's claims — name, email, and any other granted attributes. The user is now signed in.
# 1 · RP redirects the browser to the OP authorization endpoint GET https://op.example.com/authorize? response_type=code &client_id=s6BhdRkqt3 &redirect_uri=https://rp.example.co.za/callback &scope=openid profile email &state=af0ifjsldkj &code_challenge=E9Melhoa...aQzKfYg&code_challenge_method=S256 # 2 · OP authenticates the user, redirects back with a one-time code GET https://rp.example.co.za/callback?code=SplxlOBeZQ&state=af0ifjsldkj # 3 · RP exchanges the code at the token endpoint (server-to-server) POST https://op.example.com/token grant_type=authorization_code&code=SplxlOBeZQ &redirect_uri=https://rp.example.co.za/callback&code_verifier=dBjftJeZ... # → { "access_token": "...", "token_type": "Bearer", # "id_token": "<signed JWT>", "expires_in": 3600 }
The ID Token is the heart of OIDC. Per the spec, it "represents the outcome of an authentication process. It contains at a bare minimum an identifier for the user (the sub — subject — claim) and information about how and when the user authenticated." OpenID Connect "uses standard JSON Web Token (JWT) data structures when signatures are required" — so the ID Token is a JWT the RP can verify cryptographically against the OP's published keys, with no callback needed.
// ID Token — the decoded JWT payload { "iss": "https://op.example.com", // issuer — who minted it "sub": "248289761001", // subject — the stable user id "aud": "s6BhdRkqt3", // audience — your client_id "exp": 1718445600, // expiry (unix seconds) "iat": 1718442000, // issued-at "auth_time": 1718441990, // when the user actually authenticated "nonce": "n-0S6_WzA2Mj", // replay protection, echoed from the request "email": "[email protected]" }
Scopes request, claims return. The RP asks for scopes (openid is mandatory; profile, email, address, phone are the standard add-ons); the OP returns the corresponding claims — either in the ID Token or from the UserInfo endpoint. Request only the scopes you need: every extra claim is more personal data you now hold.
Every compliant OP publishes its metadata at /.well-known/openid-configuration — the authorization, token, UserInfo, and JWKS (signing-key) endpoints, supported scopes, and signing algorithms. A client library reads that one URL and configures itself. This is why swapping Google for Entra ID for Keycloak is a config change, not a rewrite — the interoperability OIDC is named for.
OIDC inherited three flows from OAuth. In 2026 the guidance has converged: use one of them, almost always.
| Flow | How it returns tokens | Use it? |
|---|---|---|
| Authorization Code + PKCE | Code via browser redirect, exchanged for tokens server-/back-channel; PKCE binds the code to the client. | Yes — the default for web apps, SPAs, mobile, and native. |
| Implicit | Tokens returned directly in the redirect URL fragment. | No — deprecated; tokens leak via URLs / history. |
| Hybrid | Some tokens from the authorization endpoint, some from the token endpoint. | Niche — specific OP/RP needs only. |
PKCE (Proof Key for Code Exchange, RFC 7636) stops an intercepted authorization code from being redeemed by an attacker — the client proves it's the same party that started the flow. Once a mobile-only mitigation, the OAuth 2.0 Security Best Current Practice (RFC 9700) and the emerging OAuth 2.1 consolidation now call for PKCE on all authorization-code flows, including server-side web apps, and they retire the implicit flow entirely. If a tutorial still teaches implicit, it's out of date.
• You need to authenticate users and want to delegate password handling to a provider.
• You want "Sign in with Google / Microsoft / Apple" or enterprise SSO without bespoke integrations per provider.
• You're building an API plus first-party web / mobile / SPA clients and want one identity model across them.
• You need verifiable, stateless proof of identity (a JWT) that downstream services can check independently.
• You only need authorization for machine-to-machine API access — plain OAuth 2.0 client-credentials, no ID Token, is enough.
• The enterprise counterpart only speaks SAML — you may need both, or a broker that bridges them.
• You're tempted to implement the OP yourself — don't. Use a battle-tested OP (Entra ID, Google, Auth0, Okta, Keycloak). Identity is the wrong place to be original.
• You're treating an Access Token as an identity assertion — that's the bug OIDC exists to prevent. Verify the ID Token instead.
Delegating identity shrinks your POPIA surface. POPIA holds you to "reasonable technical and organisational measures" (s19) over personal information. When the OpenID Provider holds the credentials and your app holds only a verifiable token, you are no longer storing passwords — one of the highest-value targets simply isn't in your database. That is a genuine, defensible reduction in risk.
Minimise the claims you request. POPIA's minimality principle maps cleanly onto OIDC scopes: ask only for openid plus the specific claims the feature needs. Don't request profile, email, address, and phone by reflex — every claim you pull is personal data you must now justify, secure, and account for.
Residency lives at the provider, not the protocol. OIDC is just the handshake; where identity data sits depends on the OP. For SA-resident workloads, the common OPs are Microsoft Entra ID (Azure has a South Africa North region — see tech/microsoft) and Google Identity (Workspace / Cloud Identity — see tech/google). Self-hosted Keycloak in africa-south1 / af-south-1 keeps the whole identity plane in-region when that's the requirement.
Verify at the edge. A common SA-friendly pattern is to terminate OIDC / JWT verification in a Cloudflare Worker in front of the app (tech/cloudflare/workers) — the token is checked against the OP's JWKS at a PoP close to the user, and only authenticated requests reach the origin.