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:

🎫 The Movie Ticket vs. Passport Analogy

"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 / GapTechnical CauseSecurity 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

DimensionSAML 2.0OAuth 2.0OpenID Connect (OIDC)
Protocol PurposeEnterprise SSO & FederationDelegated AuthorizationModern Identity & SSO
Token FormatSigned XML AssertionOpaque string or JWTJSON Web Token (ID Token)
Transport LayerHTTP POST XML BindingHTTPS REST / JSONHTTPS REST / JSON
Metadata DiscoveryManual XML file downloadNone (Vendor defined)Dynamic Discovery JSON
Mobile & SPA SupportPoor (Heavy XML payloads)ExcellentExcellent
Assurance LevelsAuthnContextClassRefNoneacr 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"]
}
⛔ Critical Security Rule:

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:

🔐 How at_hash Protects Access Tokens
  1. The Authorization Server computes the SHA-256 hash of the ASCII Access Token: hash = SHA256(access_token).
  2. It takes the left-most half of the hash (128 bits for SHA-256) and Base64URL-encodes it.
  3. It embeds this string into the ID Token as the at_hash claim.
  4. 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 NameJSON TypeDescription & Purpose
nameStringFull display name of the user (e.g. "Alice Chen").
given_nameStringFirst / given name (e.g. "Alice").
family_nameStringLast / surname / family name (e.g. "Chen").
middle_nameStringMiddle name or initial.
nicknameStringCasual display name or handle.
preferred_usernameStringPrimary login handle / corporate username (e.g. alice.chen@company.com).
profileString (URL)URL of the user's online profile page.
pictureString (URL)URL of the user's avatar / profile photo.
websiteString (URL)URL of the user's personal or company website.
genderStringGender representation.
birthdateStringBirthday formatted as YYYY-MM-DD (ISO 8601).
zoneinfoStringTimezone (e.g. America/Los_Angeles, Europe/London).
localeStringLocale string (e.g. en-US, fr-CA).
updated_atNumberUnix 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 TypeWhat 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"]
}
🌐 How BYOI (Bring Your Own Identity) Works
  1. User types their corporate email: alice@acmecorp.com.
  2. App extracts domain acmecorp.com and fetches https://acmecorp.com/.well-known/openid-configuration.
  3. App dynamically discovers Acme's authorization and JWKS endpoints.
  4. 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_typeFlow CategoryFront-Channel ArtifactsBack-Channel ArtifactsBest 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.