OpenID Connect (OIDC)

OIDC is an identity layer built on top of OAuth 2.0, providing authentication capabilities in addition to OAuth's authorization.

OAuth 2.0 (Authorization) + Identity Layer = OIDC (Authentication)

Key Differences from OAuth 2.0

OAuth 2.0 OIDC
Authorization framework Authentication protocol
Gives "access token" Gives "ID token" (JWT)
No user info Provides user identity information

ID Token Structure

```json { "iss": "https://server.example.com", "sub": "24400320", "aud": "s6BhdRkqt3", "nonce": "n-0S6_WzA2Mj", "exp": 1311281970, "iat": 1311280970, "auth_time": 1311280969, "acr": "urn:mace:incommon:iap:silver" } ```

Authentication Flow

  1. User initiates authentication
  2. Client app redirects to OIDC provider
  3. User authenticates with provider
  4. Provider returns ID token and optionally access token
  5. Client validates ID token
  6. Client uses claims from ID token to authenticate user

Authorization Endpoint

# OIDC Authorization Endpoint The Authorization Endpoint is used to interact with the resource owner and obtain an authorization grant. It's used for the Authorization Code flow and Implicit flow. ## Endpoint URL ``` GET /authorize ``` ## Parameters | Parameter | Required | Description | |-----------|----------|-------------| | `response_type` | Yes | Determines the flow - "code" for Authorization Code flow, "token" for Implicit flow, "id_token" for Hybrid flow | | `client_id` | Yes | The client identifier registered with the provider | | `redirect_uri` | Recommended | Where to redirect the user after authorization | | `scope` | Recommended | Space-separated list of scopes (openid is required for OIDC) | | `state` | Recommended | Random value used to prevent CSRF attacks | | `nonce` | Required for Implicit/ Hybrid | Random value used to mitigate replay attacks | ## Example Request ``` GET /authorize? response_type=code& client_id=s6BhdRkqt3& redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb& scope=openid%20profile%20email& state=af0ifjsldkj& nonce=n-0S6_WzA2Mj ``` ## Response The authorization server redirects the user-agent back to the client with either: - An authorization code (in the Authorization Code flow) - Access token and/or ID token (in the Implicit flow)

Token Endpoint

# OIDC Token Endpoint The Token Endpoint is used by the client to obtain an access token, ID token, and refresh token by presenting its authorization grant or refresh token. ## Endpoint URL ``` POST /token ``` ## Parameters (Authorization Code Flow) | Parameter | Required | Description | |-----------|----------|-------------| | `grant_type` | Yes | Must be set to "authorization_code" | | `code` | Yes | The authorization code received from the authorization endpoint | | `redirect_uri` | Yes | Must match the redirect_uri used in the authorization request | | `client_id` | Yes | The client identifier | | `client_secret` | Yes | The client secret (for confidential clients) | ## Example Request ``` POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=SplxlOBeZQQYbYS6WxSbIA& redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb& client_id=s6BhdRkqt3& client_secret=7Fjfp0ZBr1KtDRbnfVdmIw ``` ## Example Response ``` HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token": "SlAV32hkKG", "token_type": "Bearer", "refresh_token": "8xLOxBtZp8", "expires_in": 3600, "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ..." } ``` ## Refresh Token Request ``` POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=8xLOxBtZp8& client_id=s6BhdRkqt3& client_secret=7Fjfp0ZBr1KtDRbnfVdmIw

Discovery Document (Well-Known URL)

Before a client app can talk to an OIDC provider, it needs to know where the endpoints live. The Discovery Document is a JSON file at a standard location that tells the client everything it needs:

```http GET /.well-known/openid-configuration HTTP/1.1 Host: server.example.com ```

The response includes every endpoint URL (authorization_endpoint, token_endpoint, jwks_uri, etc.) plus what features the server supports. The /.well-known/ prefix is a standard - it means this URL is always in the same place, on every server. No guessing.

Why this matters: your app only needs to know the provider's base URL. Everything else is auto-discovered. If the provider adds a new endpoint or algorithm, the discovery document updates and your app adapts - no code changes needed.

→ Full deep-dive on the Discovery Document

Discovery Document

# OIDC Discovery Document If you've ever set up a social login (Sign in with Google, Apple, etc.), at some point you had to enter URLs like "Authorization Endpoint" and "Token Endpoint" into a config screen. The Discovery Document makes that whole process automatic. ## The URL ``` GET https://auth.server.com/.well-known/openid-configuration ``` The `/.well-known/` prefix is a standard pattern - it means "this URL is always in the same place, on every server." No guessing where the config lives. ## What It Does The Discovery Document is a JSON file that tells client apps everything they need to know about the OpenID Connect provider. It's like a restaurant menu - one glance tells you what's available and where to find it. A client can bootstrap itself with just one URL. Here's how: 1. The client knows the provider's base URL (e.g., `https://accounts.google.com`) 2. It appends `/.well-known/openid-configuration` 3. The response tells it every endpoint URL, supported feature, and capability 4. The client configures itself automatically - no manual setup needed ## What You Get Back ```json HTTP/1.1 200 OK Content-Type: application/json { "issuer": "https://server.example.com", "authorization_endpoint": "https://server.example.com/authorize", "token_endpoint": "https://server.example.com/token", "userinfo_endpoint": "https://server.example.com/userinfo", "jwks_uri": "https://server.example.com/jwks", "scopes_supported": [ "openid", "profile", "email" ], "response_types_supported": [ "code", "token", "id_token" ], "subject_types_supported": [ "public" ], "id_token_signing_alg_values_supported": [ "RS256" ] } ``` ## What It Tells You | Field | What It Means | |-------|---------------| | `issuer` | Who runs this server - the unique identifier for this provider | | `*_endpoint` | The actual URLs for each OIDC endpoint | | `jwks_uri` | Where to find public keys for token verification | | `*_supported` | What features this server supports (scopes, algorithms, etc.) | ## Why This Matters **No more hardcoding URLs.** Your app only needs to know one thing - the provider's issuer URL. Everything else is discovered automatically. If the provider moves an endpoint or adds support for a new algorithm, the discovery document updates and your app adapts without a code change. It's also how libraries and SDKs handle "one-click" OIDC setup. Ever wondered how a social login button just works? It starts here.

UserInfo Endpoint

OIDC adds a UserInfo endpoint to retrieve additional user information:

```http GET /userinfo HTTP/1.1 Host: server.example.com Authorization: Bearer SlAV32hkKG ```

UserInfo Endpoint

# OIDC UserInfo Endpoint The UserInfo Endpoint is an OAuth 2.0 protected resource that returns claims about the authenticated end-user. ## Endpoint URL ``` GET /userinfo ``` ## Requirements - Requires a valid access token - Uses Bearer token authorization - Returns claims about the authenticated user ## Example Request ``` GET /userinfo HTTP/1.1 Host: server.example.com Authorization: Bearer SlAV32hkKG ``` ## Example Response ``` HTTP/1.1 200 OK Content-Type: application/json { "sub": "248289761001", "name": "Jane Doe", "given_name": "Jane", "family_name": "Doe", "preferred_username": "j.doe", "email": "janedoe@example.com", "picture": "http://example.com/janedoe/me.jpg" } ``` ## Standard Claims | Claim | Description | |-------|-------------| | `sub` | Subject - Identifier for the End-User | | `name` | End-User's full name | | `given_name` | Given name(s) or first name | | `family_name` | Surname(s) or last name | | `middle_name` | Middle name(s) | | `nickname` | Casual name of the End-User | | `preferred_username` | Shorthand name by which the End-User wishes to be referred | | `profile` | Profile page URL | | `picture` | Profile picture URL | | `website` | Web page or blog URL | | `email` | End-User's preferred e-mail address | | `email_verified` | True if the e-mail address has been verified | | `gender` | End-User's gender | | `birthdate` | End-User's birthday | | `zoneinfo` | Time zone | | `locale` | Locale | | `phone_number` | Preferred telephone number | | `phone_number_verified` | True if the phone number has been verified | | `address` | End-User's preferred postal address | | `updated_at` | Time the information was last updated

JWKS Endpoint (Key Verification)

When your app gets an ID token, how does it know the token wasn't forged? The JWKS (JSON Web Key Set) endpoint publishes the server's public keys so you can verify the signature.

```http GET /jwks HTTP/1.1 Host: server.example.com ```

The response contains one or more public keys, each with a unique kid (Key ID). The token's header includes its own kid, so your app matches them up: find the right key, verify the signature, done.

Why this matters: key rotation works without downtime. The server adds new keys to the JWKS set and removes old ones anytime. Your app always picks the right key at verification time - no manual updates, no service interruptions.

JWKS Endpoint

# JWKS Endpoint When your app receives an ID token, how does it know the token is legit and not forged? That's where the JWKS Endpoint comes in. ## The URL ``` GET https://auth.server.com/jwks ``` ## What It Does The JWKS (JSON Web Key Set) endpoint publishes the **public keys** that the authorization server uses to sign tokens. Think of it like a public key directory - anyone can look up the keys here, but only the server holds the private keys needed to create signatures. Here's the flow: 1. Your app gets an ID token back from the token endpoint 2. The token's header says "hey, I was signed with key #abc123" (via the `kid` field) 3. Your app fetches `GET /jwks` to get the server's public keys 4. Your app finds the matching key and verifies the signature 5. If the signature checks out, you know the token is authentic ## What You Get Back ```json HTTP/1.1 200 OK Content-Type: application/json { "keys": [ { "kid": "key1", "kty": "RSA", "alg": "RS256", "use": "sig", "n": "0vx7agoebGcQSuuPiLJXZptN9nndzPwTi...", "e": "AQAB" } ] } ``` ## The Key Fields | Field | What It Means | |-------|---------------| | `kid` | Key ID - this is how the token tells you which key to use | | `kty` | Key type - usually RSA or EC (the crypto algorithm family) | | `alg` | The specific signing algorithm (RS256 = RSA with SHA-256) | | `use` | What the key is for - `sig` means signatures, `enc` means encryption | | `n` | The RSA modulus (the big number that makes RSA work) | | `e` | The RSA public exponent (usually "AQAB" = 65537) | ## Why This Matters **Key rotation without downtime.** The server can add new keys to the JWKS set and remove old ones anytime. Since your app looks up the key by `kid` at verification time, you never need a manual update. The server rolls its keys, and your app just follows along. ## The Verification Checklist 1. Grab the JWKS from `GET /jwks` 2. Match the `kid` in the token header to a key in the set 3. Use that public key to verify the token's signature 4. Check the usual claims (expiration, issuer, audience) No match found for the `kid`? The token is suspect - reject it.

Pros & Cons

  • ✅ Standardized authentication
  • ✅ Built on proven OAuth 2.0 foundation
  • ✅ Provides identity information
  • ✅ Supports multiple signing algorithms
  • ❌ Adds complexity over standard OAuth