API Security OpenID Connect

OAuth 2.0 was designed for authorization — granting access to resources. It was never designed to tell you who the user actually is. OpenID Connect (OIDC) fills that gap. It adds a standardized identity layer on top of OAuth 2.0, making it possible to authenticate users across different systems with a single login.

Every time you use "Sign in with Google," "Login with Apple," or "Connect with Microsoft," you are using OpenID Connect.

Why OAuth 2.0 Alone Is Not Enough for Authentication

OAuth 2.0 answers: "Does this application have permission to access this resource?" It does not answer: "Is this the user Meera Reddy, born in 1990, with email meera@example.com?"

The Gap in OAuth 2.0:

OAuth gives you an access token.
Access token says: "This app can read the user's contacts."
It does NOT say: "The user is Meera Reddy."

Before OIDC, developers abused OAuth to do authentication:
  1. Get access token from Google OAuth
  2. Call Google's userinfo endpoint: GET /userinfo
  3. Get back user details

The problem: This was not standardized.
  Different providers had different userinfo endpoints.
  Different field names ("user_name" vs "username" vs "sub").
  Different behavior, different token formats.
  Applications built fragile, provider-specific code.

OIDC standardizes this entire process.

What OpenID Connect Adds to OAuth 2.0

OIDC adds exactly three things on top of OAuth 2.0:

Addition 1: ID Token
  A JWT that contains the user's identity information.
  Returned alongside the access token after successful login.
  The application reads the ID token to know who the user is.

Addition 2: UserInfo Endpoint
  A standardized endpoint: GET /userinfo
  Accepts the access token and returns identity claims.
  Same endpoint structure across all OIDC providers.

Addition 3: Standard Scopes and Claims
  The "openid" scope triggers OIDC behavior.
  Standard scopes: openid, profile, email, address, phone
  Standard claims: sub, name, email, phone_number, birthdate, etc.

OIDC Flow Step by Step

OIDC Authorization Code Flow:

1. App redirects user to Identity Provider (IdP):
   GET https://accounts.google.com/oauth/authorize
     ?client_id=app123
     &redirect_uri=https://myapp.com/callback
     &scope=openid profile email         ← "openid" triggers OIDC
     &response_type=code
     &state=randomstate456
     &nonce=randomnonce789               ← New: prevents replay attacks

2. User logs in at Google and consents.

3. Google redirects back:
   GET https://myapp.com/callback?code=AUTH_CODE&state=randomstate456

4. App exchanges code for tokens:
   POST https://oauth2.googleapis.com/token
     code=AUTH_CODE
     client_id=app123
     client_secret=app_secret
     grant_type=authorization_code

5. Google returns:
   {
     "access_token": "ya29.abc...",     ← For calling Google APIs
     "id_token": "eyJhbGciOiJS...",    ← For knowing who the user is
     "expires_in": 3599,
     "scope": "openid profile email"
   }

6. App decodes and validates the ID token (it is a JWT):
   {
     "sub": "110169484474386276334",   ← Unique user ID at Google
     "name": "Meera Reddy",
     "email": "meera@example.com",
     "picture": "https://lh3.google...",
     "iss": "https://accounts.google.com",
     "aud": "app123",
     "iat": 1699900000,
     "exp": 1699903600,
     "nonce": "randomnonce789"         ← Must match nonce from step 1
   }

7. App uses "sub" as the unique user identifier in its own database.
   "sub" is stable — it never changes even if the user changes their email.

The ID Token vs The Access Token

Two very different tokens, two very different purposes:

ID Token:
  Purpose: Tells the APPLICATION who the user is.
  Audience: The client application.
  Used for: Creating sessions, logging users in.
  Never sent to: Resource server APIs.
  Contains: sub, name, email, and other identity claims.

Access Token:
  Purpose: Grants the APPLICATION permission to call APIs.
  Audience: The resource server.
  Used for: Calling protected API endpoints.
  Never sent to: The application for identity purposes.
  Contains: Scope, expiration, sometimes user ID.

Common Mistake: Sending the ID token to the resource server.
The ID token is not meant for API authorization.
The resource server does not know how to validate it properly
in the way meant for API access control.

OIDC Discovery Document

OIDC providers publish a discovery document at a standardized URL. This document describes everything a client needs to know to interact with the provider.

Discovery URL format:
  https://[PROVIDER]/.well-known/openid-configuration

Example (Google):
  https://accounts.google.com/.well-known/openid-configuration

Contents include:
{
  "issuer": "https://accounts.google.com",
  "authorization_endpoint": "https://accounts.google.com/oauth/authorize",
  "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", ...],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"]
}

The jwks_uri points to the public keys used to verify ID tokens.
Your application downloads these keys to verify signatures.

ID Token Validation Requirements

An ID token must be fully validated before trusting any identity information it contains.

ID Token Validation Checklist:

✓ 1. Signature verification
     Fetch public keys from provider's jwks_uri.
     Verify the token is signed with the matching private key.

✓ 2. Issuer (iss) check
     Must exactly match the expected provider.
     Example: "https://accounts.google.com" — no trailing slash!

✓ 3. Audience (aud) check
     Must contain your application's client_id.
     Prevents using tokens intended for another application.

✓ 4. Expiration (exp) check
     Token must not be expired.

✓ 5. Nonce check
     If you sent a nonce in the auth request, the ID token must
     contain the same nonce value.
     Prevents replay attacks where old tokens are reused.

✓ 6. At Hash (at_hash) check (if present)
     Links the ID token to the access token.
     Prevents token substitution attacks.

OIDC Security Vulnerabilities

Vulnerability 1: Accepting Any Token Without Audience Validation

Scenario:
  AppA uses OIDC with Google. client_id = "app_a_123"
  AppB uses OIDC with Google. client_id = "app_b_456"

  A user logs into AppB and gets an ID token with:
    "aud": "app_b_456"

  If AppA does not validate "aud":
    Attacker sends AppB's ID token to AppA.
    AppA accepts it.
    Attacker logs into AppA as the victim user.

Fix: Always verify that "aud" contains YOUR client_id exactly.

Vulnerability 2: Not Verifying the Nonce

Without nonce verification:
  Attacker captures a victim's ID token from a previous login.
  Initiates a new login flow for the victim.
  Injects the old ID token into the callback.
  Application processes it as a fresh login.
  Attacker authenticates as the victim.

Fix: Generate a new random nonce for every login attempt.
     Store it in the user's session.
     Verify the returned ID token contains the same nonce.

Vulnerability 3: Using Email Instead of sub as the User Identifier

The "sub" claim is a permanent, unique identifier for a user
at a specific identity provider. It never changes.

Email addresses can change:
  User changes email from old@gmail.com to new@gmail.com.
  At some providers, the email claim updates.
  An attacker who registers new@gmail.com at a different provider
  may get an ID token with email: new@gmail.com but a different sub.

If your app uses email as the primary identifier:
  User changes email → might get linked to wrong account
  Or attacker with matching email gets into victim's account

Fix: ALWAYS use "sub" (combined with "iss") as the unique identifier.
     The pair (iss, sub) is globally unique and stable.

Vulnerability 4: Implicit Flow Vulnerabilities

The OIDC Implicit flow returns the ID token directly in the URL fragment after the user authenticates. This was used for single-page apps before PKCE existed. Tokens in URLs appear in browser history, server logs, and referrer headers — major exposure risks.

Modern guidance: Always use Authorization Code flow with PKCE. Never use the Implicit flow for new applications.

Federation and Single Sign-On with OIDC

OIDC enables Single Sign-On (SSO) across multiple applications. A user logs in once at the Identity Provider and accesses all connected applications without logging in again.

OIDC SSO Architecture:

                    ┌─────────────────────┐
                    │  Identity Provider  │
                    │  (Google, Okta,     │
                    │   Azure AD, etc.)   │
                    └──────────┬──────────┘
                               │ Issues ID tokens
              ┌────────────────┼────────────────┐
              ↓                ↓                ↓
         ┌────────┐      ┌──────────┐    ┌──────────┐
         │ App A  │      │  App B   │    │  App C   │
         │ (CRM)  │      │  (Wiki)  │    │  (HR)    │
         └────────┘      └──────────┘    └──────────┘

User logs in once at the Identity Provider.
All three apps trust the IdP's ID tokens.
User accesses all three apps without separate logins.

Security consideration for SSO: A compromised IdP account gives attackers access to ALL connected applications simultaneously. This makes IdP account security — strong passwords, MFA, suspicious login alerts — critically important in an OIDC SSO environment.

Building Your Own OIDC Provider vs Using a Third-Party

Most applications should use an established Identity Provider rather than building their own OIDC implementation. Building a secure OIDC implementation correctly is extraordinarily complex. Security bugs in home-built identity systems have caused major breaches.

Established providers include Auth0, Okta, AWS Cognito, Azure Active Directory, Keycloak (self-hosted), and Google Identity Platform. These services handle token issuance, key rotation, brute force protection, MFA, and compliance requirements out of the box.

Key Points

  • OIDC adds a standardized identity layer on top of OAuth 2.0. The "openid" scope triggers OIDC behavior.
  • The ID token is a JWT that tells the application who the user is. It is different from the access token, which authorizes API calls.
  • Always use the "sub" claim (not email) as the stable, unique user identifier.
  • Validate ID tokens completely: signature, issuer, audience, expiration, and nonce.
  • The OIDC discovery document provides all endpoints and key locations in a standardized format.
  • Use established Identity Providers rather than building custom OIDC implementations.
  • SSO through OIDC is powerful but means a compromised IdP account gives access to all connected apps — making IdP security extremely important.

Leave a Comment