OAuth 2.0 / OpenID Connect (OIDC) Cheatsheet
Core Concepts
- Authorization Server (AS): Issues tokens (a.k.a. Identity Provider in OIDC).
- Resource Owner (RO): The user.
- Client: Your app (confidential = has a secret; public = no secret).
- Resource Server (RS): API that validates access tokens.
- Scopes: What the client is asking to access (e.g.,
read:invoices
,openid
). - Claims: Statements about a subject (e.g.,
sub
,email
).
Standard Endpoints
Purpose | OAuth 2.0 | OIDC Discovery |
---|---|---|
Authorization | /authorize |
/.well-known/openid-configuration |
Token | /token |
– |
Revocation | /revoke |
– |
Introspection | /introspect |
– |
JWKS (keys) | – | /jwks from discovery |
UserInfo | – | /userinfo |
Discovery doc gives you all URLs + supported algs/scopes.
Grants / Flows (When to Use)
Flow | Use It For | Notes |
---|---|---|
Auth Code + PKCE | Web apps, SPAs, mobile | Default modern choice. Avoid implicit. |
Client Credentials | Machine-to-machine | No user; scopes reflect app privileges. |
Device Code | TVs/CLIs w/ no browser | User types code on secondary device. |
Refresh Token | Long-lived sessions | Use Rotation + Binding (MTLS/DPoP) if possible. |
CIBA (advanced) | Decoupled auth on a 2nd device | OIDC extension; AS notifies user device. |
Do not use: Implicit flow (deprecated for SPAs). Password Grant (removed in OAuth 2.1 draft).
Minimal “Auth Code + PKCE” Sequence
-
Create verifier & challenge
-
Verifier: random 43–128 chars.
- Challenge:
BASE64URL(SHA256(verifier))
. - Send user to /authorize with
response_type=code
,code_challenge
,code_challenge_method=S256
. - Exchange code at /token with
code_verifier
. - Use access token for APIs; verify ID token (OIDC).
/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb&
scope=openid%20profile%20email%20api.read&
state=opaqueCsrf123&
code_challenge=...&
code_challenge_method=S256
Token exchange (POST x-www-form-urlencoded):
grant_type=authorization_code&
code=AUTH_CODE_FROM_CB&
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb&
client_id=CLIENT_ID&
code_verifier=ORIGINAL_VERIFIER
ID Token (OIDC)
- Format: JWT signed by AS (
alg
typicallyRS256
/ES256
). -
Must verify:
-
Signature (via AS JWKS)
iss
equals issueraud
includes yourclient_id
exp
/iat
validnonce
matches (if sent)- Common claims:
sub
(stable user id),auth_time
,amr
,acr
,email
,email_verified
.
Access Token
- Format: Opaque or JWT.
- Audience (
aud
): API identifier. -
Validation (at API):
-
If JWT: validate signature, issuer, audience,
exp
, optionallyscope
. - If opaque: call introspection at AS.
- Never rely on ID token at the API; use access token.
UserInfo (OIDC)
GET/POST /userinfo
withAuthorization: Bearer <access_token>
- Returns user claims authorized by scopes (
profile
,email
, etc.). - Prefer ID token for login, UserInfo for fresh profile data.
Scopes (Quick Picks)
- OIDC:
openid
(required), thenprofile
,email
,address
,phone
,offline_access
(refresh). - API: app-specific like
api.read
,payments:write
.
Only request what you need; align with least privilege.
Tokens: Lifetimes & Rotation
Token | Typical TTL | Notes |
---|---|---|
Access Token | 5–15 min | Short-lived; renewable via refresh token. |
Refresh Token | Days–months | Use rotation + one-time use detection. |
ID Token | \~5–60 min | Used by client; not for API authorization. |
- Revoke on logout or compromise.
- Bind tokens where possible (MTLS or DPoP for sender-constrained tokens).
Security Musts
- Always use HTTPS.
- PKCE for all public clients (SPAs, mobile).
- Use a strong, unpredictable
state
(CSRF) andnonce
(replay). - No client secret in SPAs/mobile.
- Enforce exact redirect URI matching (or allow-listed prefixes with care on mobile).
- Enable refresh token rotation; detect reuse.
- Prefer SameSite=Lax/Strict and
HttpOnly
cookies for web sessions. - If using JWT access tokens: keep them short-lived and include minimal claims.
- JAR/JARM (advanced): signed requests/responses for high assurance.
- PAR (pushed authorization requests) to avoid front-channel tampering.
Error Handling (Common)
At /authorize
(front-channel):
error=access_denied | invalid_request | login_required | consent_required
- Preserve and reflect
state
on your callback.
At /token
(back-channel; JSON):
Handle gracefully; never expose raw server errors to users.
Sample: Validate an ID Token (Pseudo)
// 1) Fetch OIDC discovery -> get jwks_uri
// 2) Fetch JWKS, find key by 'kid' in token header
// 3) Verify signature (RS256/ES256), then claims:
assert(payload.iss === issuer)
assert(payload.aud.includes(client_id))
assert(now < payload.exp)
assert(storedNonce === payload.nonce)
Sample: Call an API with Access Token
API should check:
- Signature/issuer/audience/exp (JWT) or introspect (opaque).
- Required scopes (e.g.,
customers:read
).
Device Code Flow (Quick)
- Client gets
device_code
,user_code
,verification_uri
. - Display code & URI to user; start polling token endpoint.
- After user approves on another device, token endpoint returns tokens.
Polling respects interval
; stop on access_denied
, expired_token
.
Logout (RP-Initiated Logout)
- OIDC supports redirect to the AS logout endpoint (if provided).
- Consider front-channel or back-channel logout to clear other sessions.
- Locally clear app session, cookies, and cached tokens.
Multi-tenancy Tips
- Tenant in issuer (
iss = https://idp.example.com/{tenant}
) or intid
claim. - Separate client_ids per tenant when possible.
- Partition keys (JWKS) and redirect URIs per tenant.
Audiences & APIs
- Use distinct audience values per API (e.g.,
api://billing
). - Ask for tokens per API you call (or use token exchange if supported).
PKCE Quick Generator (CLI-ish Pseudocode)
# verifier: 43–128 chars URL-safe
VERIFIER=$(openssl rand -base64 64 | tr '+/' '-_' | tr -d '=' | cut -c1-64)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
Common Pitfalls
- ❌ Using ID token to call APIs.
- ❌ Storing tokens in localStorage (XSS risk). Prefer
HttpOnly
cookies or in-memory. - ❌ Skipping state/nonce.
- ❌ Long-lived access tokens without revocation.
- ❌ Not pinning to audience at APIs.
- ❌ Reusing refresh tokens; no rotation/reuse detection.
Minimal Config Checklist
- Get issuer and client_id from IdP.
- Set exact redirect URIs (and logout URIs).
- Enable Auth Code + PKCE; disable implicit/password grants.
- Configure scopes (incl.
openid
for OIDC). - Short access token TTL; enable refresh rotation.
- Validate ID token and access token correctly.
- Use DPoP or MTLS if supported for higher security.
- Implement graceful error handling.
Quick Glossary
- DPoP: Proof-of-Possession for sender-constrained tokens.
- PAR: Pushed Authorization Requests (send request params via back-channel).
- JAR/JARM: JWT-secured Authorization (Requests/Responses).
- CIBA: Client-Initiated Backchannel Auth (decoupled login).
- JWKS: JSON Web Key Set (public keys for verifying JWTs).
References You’ll Want Handy (generic)
- OAuth 2.0 (RFC 6749), Bearer (RFC 6750)
- OAuth 2.0 for Native Apps (RFC 8252)
- PKCE (RFC 7636)
- Token Revocation (RFC 7009), Introspection (RFC 7662)
- OAuth 2.0 Security BCP (RFC 9126)
- OAuth 2.1 (draft)
- OpenID Connect Core / Discovery / Logout