Skip to content

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

  1. Create verifier & challenge

  2. Verifier: random 43–128 chars.

  3. Challenge: BASE64URL(SHA256(verifier)).
  4. Send user to /authorize with response_type=code, code_challenge, code_challenge_method=S256.
  5. Exchange code at /token with code_verifier.
  6. 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 typically RS256/ES256).
  • Must verify:

  • Signature (via AS JWKS)

  • iss equals issuer
  • aud includes your client_id
  • exp/iat valid
  • nonce 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, optionally scope.

  • If opaque: call introspection at AS.
  • Never rely on ID token at the API; use access token.

UserInfo (OIDC)

  • GET/POST /userinfo with Authorization: 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), then profile, 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) and nonce (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):

{
  "error": "invalid_grant",
  "error_description": "Code has expired or already been used"
}

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

curl https://api.example.com/customers \
  -H "Authorization: Bearer $ACCESS_TOKEN"

API should check:

  • Signature/issuer/audience/exp (JWT) or introspect (opaque).
  • Required scopes (e.g., customers:read).

Device Code Flow (Quick)

  1. Client gets device_code, user_code, verification_uri.
  2. Display code & URI to user; start polling token endpoint.
  3. 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 in tid 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