OpenID Connect (OIDC)
Understanding the modern identity layer built on OAuth 2.0, the history of federated identity, solving the pseudo-authentication problem, ID Token verification, dynamic discovery, and internet-scale BYOI federation.
The Evolution of Identity Protocols
The web's approach to identity and authentication progressed through 4 distinct eras:
1. Proprietary Silos
1990s – Early 2000s
Every app required its own username/password database or proprietary vendor LDAP connectors. Zero interoperability.
2. SAML 2.0 (XML)
2005 – Enterprise Standard
Heavy XML assertions and manual certificate federation. Reliable for enterprise web SSO, but brittle for mobile apps and SPAs.
3. OAuth 2.0 (RFC 6749)
2012 – REST/JSON AuthZ
Lightweight delegated authorization using HTTP and JSON. Left token formats and authentication semantics undefined.
4. OpenID Connect (OIDC)
2014+ – Modern Standard
Combines OAuth 2.0 transport with signed JWT ID Tokens, standardized scopes, discovery, and dynamic federation.
The Pseudo-Authentication Problem
Before OpenID Connect, developers attempted to use raw OAuth 2.0 Access Tokens as proof of user identity. As documented by oauth.net and Okta's identity architecture team:
"Using an Access Token as proof of identity is like using a movie ticket as a passport:"
- Movie Ticket (Access Token): Proves someone paid for Seat 14B in Theater 3. The usher does not care who you are, only that you have a valid ticket to enter the room.
- Passport (ID Token): An official, tamper-proof credential issued by an authority stating your full legal name, date of birth, photo, and citizenship.
Why Raw OAuth Fails for Authentication:
| Vulnerability / Gap | Technical Cause | Security Consequence |
|---|---|---|
| Audience Mismatch | Access token audience (aud) is the backend Resource Server, not the Client application. | Token Replay Attack: A malicious App A can take an Access Token issued to it and replay it to log into App B. |
| Opaque Format | OAuth 2.0 intentionally left access token formats undefined (often random hexadecimal strings). | Client cannot safely inspect user claims, timestamps, or issuer signatures locally without an extra server call. |
| No Standard User Info Schema | Every provider returned profile data in incompatible JSON shapes (e.g., Google vs GitHub vs Facebook). | Developers had to write custom parsers and maintain brittle API adapters for every identity provider. |
What is OpenID Connect (OIDC Core 1.0)?
OpenID Connect combined the best features of OAuth 2.0, Facebook Connect, and SAML 2.0 to establish a standardized, lightweight identity layer:
1. The ID Token
A cryptographically signed JWT intended exclusively for the Client application to prove the user authenticated.
2. UserInfo Endpoint
Standardized REST endpoint (GET /userinfo) returning validated, normalized JSON user profile claims.
3. Dynamic Discovery
JSON metadata at /.well-known/openid-configuration eliminating manual federation configuration.
SAML 2.0 vs OIDC vs OAuth 2.0
| Dimension | SAML 2.0 | OAuth 2.0 | OpenID Connect (OIDC) |
|---|---|---|---|
| Protocol Purpose | Enterprise SSO & Federation | Delegated Authorization | Modern Identity & SSO |
| Token Format | Signed XML Assertion | Opaque string or JWT | JSON Web Token (ID Token) |
| Transport Layer | HTTP POST XML Binding | HTTPS REST / JSON | HTTPS REST / JSON |
| Metadata Discovery | Manual XML file download | None (Vendor defined) | Dynamic Discovery JSON |
| Mobile & SPA Support | Poor (Heavy XML payloads) | Excellent | Excellent |
| Assurance Levels | AuthnContextClassRef | None | acr and amr claims |
OIDC Actors (Standard Terminology)
- End-User (Resource Owner): The human whose identity is being verified.
- Relying Party (RP / Client): The application requesting identity proof from the OpenID Provider.
- OpenID Provider (OP / Authorization Server): The identity server (Okta, Entra ID, Google) that authenticates the user and issues ID Tokens.
The ID Token Anatomy (RFC 7519 & OIDC Core 1.0)
The ID Token is a signed JSON Web Token containing standardized identity assertions:
// Decoded ID Token Payload (JSON)
{
"iss": "https://authorization-server.example.com",
"sub": "usr_998877",
"aud": "my_client_app_id",
"exp": 1755003600,
"iat": 1755000000,
"auth_time": 1754999990,
"nonce": "n-0S6_WzA2Mj",
"at_hash": "T7ij7o69gBtjo6bAJvaVBQ",
"amr": ["pwd", "mfa"],
"idp": "00o1zyyqo9bpRehCw1t7",
"name": "Alice Chen",
"email": "alice@authmatrix.local",
"email_verified": true,
"roles": ["Admin"]
} The ID Token is intended solely for the Client Application. Never send the ID Token to backend API servers in the Authorization: Bearer header. Backend APIs should only receive Access Tokens!
Cryptographic Hash Bindings: at_hash & c_hash
When tokens are issued via front-channel redirects, OIDC uses mathematical hashes inside the ID Token to prevent token injection and substitution attacks:
at_hash Protects Access Tokens- The Authorization Server computes the SHA-256 hash of the ASCII Access Token:
hash = SHA256(access_token). - It takes the left-most half of the hash (128 bits for SHA-256) and Base64URL-encodes it.
- It embeds this string into the ID Token as the
at_hashclaim. - When the client receives both the ID Token and Access Token in the browser, it hashes the Access Token and verifies it matches
at_hash. This mathematically proves that the Access Token came from the exact same issuance as the signed ID Token.
The 14 Standard OIDC Profile Claims
Requesting scope=profile grants access to the full standardized profile claim schema defined in OIDC Core Section 5.1:
| Claim Name | JSON Type | Description & Purpose |
|---|---|---|
name | String | Full display name of the user (e.g. "Alice Chen"). |
given_name | String | First / given name (e.g. "Alice"). |
family_name | String | Last / surname / family name (e.g. "Chen"). |
middle_name | String | Middle name or initial. |
nickname | String | Casual display name or handle. |
preferred_username | String | Primary login handle / corporate username (e.g. alice.chen@company.com). |
profile | String (URL) | URL of the user's online profile page. |
picture | String (URL) | URL of the user's avatar / profile photo. |
website | String (URL) | URL of the user's personal or company website. |
gender | String | Gender representation. |
birthdate | String | Birthday formatted as YYYY-MM-DD (ISO 8601). |
zoneinfo | String | Timezone (e.g. America/Los_Angeles, Europe/London). |
locale | String | Locale string (e.g. en-US, fr-CA). |
updated_at | Number | Unix timestamp when the profile was last modified. |
Where Do Claims Go? (ID Token vs /userinfo)
OIDC balances token size with performance based on the response_type and scope requested:
| Flow / Response Type | What is returned in the ID Token? | How to get remaining Profile Claims? |
|---|---|---|
| Identity Only ( response_type=id_token) | ALL requested profile claims (name, email, locale, etc.) are embedded directly inside the ID Token JWT. | Not applicable — no access token was issued to query /userinfo. |
| Tokens + API Access ( response_type=code or id_token token) | Minimal claims only (sub, iss, aud, exp, nonce, at_hash) to keep token header sizes lightweight for HTTP headers. | Call GET /userinfo using the Access Token: Authorization: Bearer <access_token> to retrieve full profile claims. |
Dynamic Discovery & BYOI (Bring Your Own Identity)
OIDC enables internet-scale dynamic federation without pre-sharing XML certificates. An application can discover everything about an IdP by querying /.well-known/openid-configuration:
GET /.well-known/openid-configuration HTTP/1.1
Host: accounts.example.com
// Response
{
"issuer": "https://accounts.example.com",
"authorization_endpoint": "https://accounts.example.com/oauth2/v1/authorize",
"token_endpoint": "https://accounts.example.com/oauth2/v1/token",
"userinfo_endpoint": "https://accounts.example.com/oauth2/v1/userinfo",
"jwks_uri": "https://accounts.example.com/oauth2/v1/keys",
"response_types_supported": ["code", "id_token", "code id_token"],
"subject_types_supported": ["public", "pairwise"],
"id_token_signing_alg_values_supported": ["RS256"]
} - User types their corporate email:
alice@acmecorp.com. - App extracts domain
acmecorp.comand fetcheshttps://acmecorp.com/.well-known/openid-configuration. - App dynamically discovers Acme's authorization and JWKS endpoints.
- User logs in at Acme IdP and returns with an ID Token signed by Acme's public keys.
5-Step OpenID Connect Flow
sequenceDiagram
autonumber
participant RP as 💻 Relying Party (App)
participant OP as 🌐 OpenID Provider (IdP)
participant UserInfo as 👤 UserInfo Endpoint
Note over RP,OP: Step 1: Dynamic Discovery (BYOI)
RP->>OP: 1. GET /.well-known/openid-configuration
OP-->>RP: 2. Metadata (jwks_uri, auth_endpoint, token_endpoint)
Note over RP,OP: Step 2: User Authentication & Code Flow
RP->>OP: 3. GET /authorize (scope=openid profile email, nonce=n-0S6)
OP-->>RP: 4. 302 Redirect with Authorization Code
Note over RP,OP: Step 3: Token Exchange & JWKS Key Fetch
RP->>OP: 5. POST /token (code, grant_type=authorization_code)
OP-->>RP: 6. Tokens { id_token (JWT), access_token }
RP->>OP: 7. GET /.well-known/jwks.json
OP-->>RP: 8. Public Verification Keys (kid, RSA Modulus, Exponent)
Note over RP: Step 4: Local ID Token Verification
Verify RS256 signature, iss, aud, exp, nonce
Note over RP,UserInfo: Step 5: (Optional) Extended Profile Fetch
RP->>UserInfo: 9. GET /userinfo (Authorization: Bearer <access_token>)
UserInfo-->>RP: 10. 200 OK { sub, name, email, picture, roles }
OIDC Response Types & Hybrid Flow Matrix
response_type | Flow Category | Front-Channel Artifacts | Back-Channel Artifacts | Best Used For |
|---|---|---|---|---|
code | Authorization Code Flow | code | id_token, access_token, refresh_token | Server-side web apps with middleware (Express, Spring Boot). |
id_token | Implicit (Identity Only) | id_token | None | Client app needing instant identity verification without API access. |
id_token token | Implicit (Full) | id_token, access_token | None | Legacy SPAs calling APIs directly (Deprecated in OAuth 2.1). |
code id_token | Hybrid Flow | code, id_token | access_token, refresh_token | Immediate client identity render + back-channel API session setup. |
code id_token token | Hybrid Flow (Full) | code, id_token, access_token | refresh_token | Client needs instant API access & server needs refresh token. |