NOTE
Most of the content in this work was generated with the assistance of AI and carefully reviewed, edited, and curated by the author. If you have found any issues with the content on this page, please do not hesitate to contact me at support@issacc.com.
OAuth 2.0 — Deep Dive Cheat Sheet
A practical, systems-architect view of OAuth 2.0 (and OIDC): roles, flows, tokens, security patterns, and troubleshooting. Copy/paste ready snippets included.
1) Mental Model (What OAuth Is/Isn’t)
- OAuth ≠ login. It’s delegation/authorization: let a client act on a user’s behalf with limited scopes and lifetimes.
- OIDC adds authentication (identity) on top of OAuth via ID Tokens.
- Trust boundaries: Authorization Server (AS) issues tokens; Resource Server (RS) validates tokens; Client holds tokens but should not learn user password.
2) Core Actors & Contracts
Actor | Responsibility | Notes |
---|---|---|
Resource Owner (User) | Grants consent | Sees consent screen for scopes |
Client (App) | Requests tokens, calls APIs | Public or confidential |
Authorization Server (AS) | Authenticates user, issues tokens | /authorize , /token , JWKS |
Resource Server (RS/API) | Validates tokens, enforces scopes | Checks iss , aud , exp , scope |
Key Artifacts: access_token
(short-lived), refresh_token
(longer-lived), scope
, client_id
(+ client_secret
for confidential clients).
3) Which Flow Should I Use?
flowchart LR A[What are you building?] -->|Browser SPA or Mobile| B[Auth Code + PKCE] A -->|Web app w/ backend| C[Auth Code] A -->|Machine-to-machine| D[Client Credentials] A -->|TV/Console/IoT| E[Device Code] B --> F[No client secret; use PKCE] C --> G[Keep secret on server only] D --> H[No user; service principal] E --> I[User types code on second device]
4) Authorization Code + PKCE Deep Walkthrough
sequenceDiagram participant U as User participant C as Client (SPA/Mobile/Web) participant AS as Authorization Server participant RS as Resource Server U->>C: Click Sign in with X C->>AS: GET /authorize (client_id, redirect_uri, response_type=code, scope, state, code_challenge) AS-->>U: Login + Consent U->>AS: Approve AS-->>C: 302 redirect (code, state) C->>AS: POST /token (code, code_verifier, client_id, redirect_uri) AS-->>C: access_token (+ refresh_token, id_token?) C->>RS: API call with Authorization: Bearer <access_token> RS-->>C: Protected data
PKCE: code_verifier
→ code_challenge = BASE64URL(SHA256(verifier))
. Prevents intercepted codes from being exchanged by attackers.
5) Token Anatomy & Validation (JWT focus)
Access Token (JWT) — typical claims
{
"iss": "https://auth.example.com/",
"sub": "user-123",
"aud": "api.example.com",
"exp": 1731788575,
"iat": 1731784975,
"scope": "email profile.read"
}
RS validation checklist
- Verify signature against AS JWKS.
- Check
exp
(not expired),nbf
(if present). - Check
aud
matches this API. - Check
iss
matches expected issuer. - Enforce scope to specific endpoints/operations.
Opaque tokens: RS calls AS /introspect
→ returns {active, scope, sub, aud, exp, ...}
.
6) OIDC (Authentication on Top)
- Adds ID Token (JWT) with identity claims (
sub
,email
,name
, etc.). - Adds
nonce
to mitigate token replay. - Endpoints:
/.well-known/openid-configuration
,userinfo
.
Use OIDC whenever you need to know who the user is, not just access their data.
7) Security Playbook
- HTTPS everywhere
- PKCE for public clients (SPAs, mobile)
- Short-lived access tokens (5–15 min)
- Refresh token rotation (revoke previous on each use)
- Least privilege scopes (ask for what you need only)
- CSRF defense via
state
in/authorize
(andnonce
for OIDC) - Audience restriction: each API has its own audience; don’t reuse tokens across APIs
- Key rotation: AS rotates signing keys; RS fetches via JWKS and caches
- Secure storage: Prefer HTTP-only, SameSite cookies or in-memory storage for browser apps; avoid
localStorage
for long-lived secrets
8) Practical Snippets
8.1 Authorization Request (browser)
GET /authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb&scope=openid%20profile.read&state=xyz&code_challenge=abc&code_challenge_method=S256 HTTP/1.1
Host: auth.example.com
8.2 Token Exchange
curl -X POST https://auth.example.com/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code' \
-d 'code=AUTH_CODE' \
-d 'client_id=CLIENT_ID' \
-d 'client_secret=CLIENT_SECRET' \
-d 'redirect_uri=https://app.example.com/cb' \
-d 'code_verifier=VERIFIER'
8.3 Calling the API
curl https://api.example.com/v1/me \
-H 'Authorization: Bearer ACCESS_TOKEN'
8.4 Introspection (opaque tokens)
curl -u CLIENT_ID:CLIENT_SECRET \
-d 'token=ACCESS_TOKEN' \
https://auth.example.com/introspect
9) Common Errors & Fixes (Field Guide)
Error | Typical Cause | Fix |
---|---|---|
invalid_grant | Bad/expired code; redirect_uri mismatch; no valid scopes granted; reused code | Ensure code is unexpired, single-use, redirect URI matches, scopes requested/approved |
invalid_client | Wrong client_id/secret, wrong auth method | Verify credentials and client auth method (basic vs body) |
invalid_scope | Scope string unknown or not allowed for client | Request only allowed scopes; check server config |
invalid_redirect_uri | Mismatch with registered URIs | Register exact redirect URI; use HTTPS |
unauthorized_client | Flow not allowed for this client | Enable the grant type in AS client settings |
invalid_token at API | Expired token; wrong audience/issuer; signature fail | Validate iss/aud/exp ; refresh token; check JWKS keys |
Salesforce/Google/etc.: If you see
invalid_grant: no valid scopes defined
, confirm the connected app scopes and that the user/org policy allows them; re-consent as needed.
10) Designing the Authorization Server (AS) — Internal View
- Endpoints:
/authorize
,/token
,/jwks.json
,/introspect
,/revoke
,/userinfo
(OIDC) - Client registration: static (console) or dynamic (RFC 7591)
- Consent UX: scope grouping, incremental auth, remembered consent
- Token service: issue JWT (sign with rotating keys), set lifetimes by flow & risk
- Refresh rotation: on
/token
with refresh grant → issue new refresh, revoke old - Revocation:
/revoke
to invalidate stolen tokens - Risk signals: IP, device, geo, anomaly detection → force reauth or step-up MFA
11) Resource Server (API) Pattern
- Fetch JWKS via
/.well-known/openid-configuration
→jwks_uri
- Cache keys; support rotation (kid switching)
- Middleware validates: signature →
exp
→iss
→aud
→scope
- Map scopes → permissions at endpoint level
- Prefer 401 for invalid/missing token; 403 for insufficient scope
Example pseudo-middleware
async function authz(req, res, next) {
const token = parseBearer(req.headers.authorization);
if(!token) return res.status(401).end();
const claims = await verifyJWT(token, jwks);
if(claims.iss !== ISS || !claims.aud.includes(API_AUD)) return res.status(401).end();
if(isExpired(claims.exp)) return res.status(401).end();
if(!hasScope(claims.scope, requiredScopeFor(req))) return res.status(403).end();
req.user = { sub: claims.sub, scope: claims.scope };
next();
}
12) Browser Storage & Cookies (SPA)
- Prefer Authorization Code + PKCE.
- Use in-memory storage for access tokens; refresh via backend or BFF (Backend-for-Frontend) pattern.
- If using cookies: set HttpOnly, Secure, SameSite=Lax/Strict; avoid storing refresh tokens in JS-accessible storage.
13) Scope Design Guidelines
- Keep scopes resource-oriented:
calendar.read
,calendar.write
vs vaguefull_access
. - Support incremental consent (start with read-only; request write later).
- Avoid overbroad default scopes.
14) Threats & Mitigations
Threat | Vector | Mitigation |
---|---|---|
Code interception | Network/MITM | PKCE; HTTPS |
CSRF on /authorize | Cross-site link | state (and nonce for OIDC) |
Token theft | XSS, log leakage | HttpOnly cookies, minimal logs, short TTL |
Audience confusion | Token used across services | Enforce aud per API |
Refresh token replay | Stolen RT | Rotation + revoke previous |
Open redirect | Misconfigured redirect_uri | Exact allowlist with HTTPS |
15) Quick Provider Template
Discovery: GET https://<issuer>/.well-known/openid-configuration
Authorize: GET <issuer>/authorize
Token: POST <issuer>/token
JWKS: GET <issuer>/.well-known/jwks.json
UserInfo (OIDC): GET <issuer>/userinfo
16) Debug Checklist (Copy/Paste)
17) Mini Glossary
- AS: Authorization Server
- RS: Resource Server (API)
- PKCE: Proof Key for Code Exchange
- JWKS: JSON Web Key Set (public keys for verifying JWTs)
- BFF: Backend-For-Frontend pattern to keep tokens off the browser