OAuth2 & OIDC — Deep Dive
Level: Beginner → Intermediate
Pre-reading: 08 · Security Summary
What Problem Does OAuth2 Solve?
Before OAuth2, apps asked users for their username/password directly to access third-party resources — a massive security risk. OAuth2 introduces delegated authorization: a user grants a client app limited access to their resources without sharing credentials.
OAuth2 is NOT an authentication protocol
OAuth2 only answers "can this app access this resource?" — not "who is this user?"
OIDC (OpenID Connect) adds the identity layer on top of OAuth2 to answer "who is this user?"
Core Roles
| Role | Description | Example |
|---|---|---|
| Resource Owner | The user who owns the data | End user |
| Client | App requesting access on behalf of the user | Your web/mobile app |
| Authorization Server (AS) | Issues tokens after authenticating the user | Auth0, Keycloak, AWS Cognito |
| Resource Server (RS) | Hosts the protected resource; validates tokens | Your API |
Authorization Code + PKCE Flow (Current Best Practice)
PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks — mandatory for public clients (SPAs, mobile apps).
sequenceDiagram
participant U as User
participant App as Client App
participant AS as Auth Server
participant RS as Resource Server
U->>App: Click Login
App->>App: Generate code_verifier + code_challenge
App->>AS: Redirect · response_type=code · code_challenge
AS->>U: Login + Consent screen
U->>AS: Credentials + approval
AS->>App: Authorization code (short-lived, single-use)
App->>AS: code + code_verifier (exchange)
AS->>App: Access Token · Refresh Token · ID Token
App->>RS: API call with Bearer Access Token
RS->>App: Protected resource
Why PKCE? The code_verifier is never sent until the token exchange — even if an attacker intercepts the authorization code, they cannot exchange it without the verifier.
Grant Types — When to Use Which
| Grant Type | Who Uses It | Has User? | Notes |
|---|---|---|---|
| Authorization Code + PKCE | Web apps, SPAs, mobile | ✅ Yes | Always use PKCE; the only correct choice for user-facing apps |
| Client Credentials | Backend services, daemons | ❌ No | client_id + client_secret exchanged for token; machine-to-machine only |
| Device Flow | TVs, CLI tools, IoT | ✅ Yes | App shows a code; user authenticates on another device |
| Implicit | (Deprecated) | ✅ Yes | Tokens returned in URL fragment; vulnerable to leakage — never use |
| Resource Owner Password | (Deprecated) | ✅ Yes | App receives password directly — defeats OAuth2's purpose |
Never use Implicit or Resource Owner Password grants
Both are deprecated in OAuth2.1. Implicit leaks tokens in browser history; ROPC bypasses the authorization server's value entirely.
OIDC — Adding Identity to OAuth2
OIDC adds an ID Token (a JWT) to the OAuth2 flow. This token tells your app who the user is.
ID Token Claims
| Claim | Meaning |
|---|---|
sub |
Subject — unique user identifier at the IdP |
iss |
Issuer — URL of the Authorization Server |
aud |
Audience — your client's client_id |
exp |
Expiry timestamp |
iat |
Issued-at timestamp |
email |
User's email (if scope email requested) |
name |
User's display name (if scope profile requested) |
OIDC Scopes
| Scope | What You Get |
|---|---|
openid |
Required — triggers OIDC; returns ID Token |
profile |
name, given_name, picture, locale |
email |
email, email_verified |
offline_access |
Refresh Token for long-lived access |
Token Validation — What Your Resource Server Must Do
graph TD
A[Receive Bearer Token] --> B{Is JWT format?}
B -->|Yes| C[Fetch JWKS from AS /.well-known/jwks.json]
C --> D{Signature valid?}
D -->|No| E[Reject 401]
D -->|Yes| F{exp in future?}
F -->|No| E
F -->|Yes| G{iss matches?}
G -->|No| E
G -->|Yes| H{aud matches?}
H -->|No| E
H -->|Yes| I[Authorized - proceed]
Use your library's validation — don't roll your own
Libraries like nimbus-jose-jwt (Java), python-jose, or jsonwebtoken (Node) handle JWKS rotation, algorithm verification, and claim checking correctly. Manual validation almost always has gaps.
Common Mistakes
| Mistake | Risk | Fix |
|---|---|---|
Not validating aud |
Token issued for App A accepted by App B | Always check aud matches your service |
| Using Implicit grant | Token leaked via browser history, referrer | Switch to Auth Code + PKCE |
| Long-lived access tokens | Stolen token valid for hours/days | Short expiry (15 min); use refresh tokens |
| Not rotating refresh tokens | Stolen refresh token lasts forever | Enable refresh token rotation at AS |
Trusting alg: none |
Attacker strips signature entirely | Explicitly allowlist expected algorithms |
SAML vs OAuth2 vs OIDC
| SAML | OAuth2 | OIDC | |
|---|---|---|---|
| Format | XML | JSON/JWT | JSON/JWT |
| Use case | Enterprise SSO | API authorization | API authentication |
| Transport | Browser redirects | HTTP redirects | HTTP redirects |
| Age | 2002 | 2012 | 2014 |
| Complexity | High | Medium | Medium |
| Best for | Legacy enterprise IdP | Delegated API access | Modern login flows |
What is the difference between OAuth2 and OIDC?
OAuth2 is an authorization framework — it grants access to resources. OIDC is an authentication layer built on top of OAuth2 — it also tells you who the user is via an ID Token (JWT). You cannot use bare OAuth2 to log in a user; you need OIDC for that.
Why is PKCE needed for SPAs if they use HTTPS?
PKCE protects against authorization code interception — a malicious app on the same device registered for the same redirect URI can steal the code. PKCE binds the code to a secret only the originating app knows (code_verifier), so the stolen code is useless without it.