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
- User initiates authentication
- Client app redirects to OIDC provider
- User authenticates with provider
-
Provider returns ID token and optionally access
token
- Client validates ID token
-
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