OAuth 2.x
Delegated authorization: let an app act on a resource without ever seeing your password.
By the end of this lesson, explain the authorization code flow and the roles of client, authorization server, and resource server.
OAuth 2.0 solves one problem: letting an application access a resource on a user's behalf without the user handing over their password. The user authorizes a scoped grant; the app receives an access token; the app calls the API with that token. The password never touches the app.
- The four roles
- Resource owner (the user), Client (the app), Authorization server (issues tokens after consent), Resource server (the API that accepts tokens).
First, see the whole system. Then we’ll open it up.
Authorization Code flow with PKCE
- The client redirects the user to the authorization server with a PKCE code challenge.
- The user authenticates and consents; the server returns a short-lived authorization code.
- The client exchanges the code plus its PKCE verifier for access and refresh tokens.
- The client calls the resource server with the access token.
flowchart TB user[Resource owner] -->|1 clicks connect| client[Client app] client -->|2 redirect + code_challenge| as[Authorization server] user -->|3 authenticates + consents| as as -->|4 authorization code| client client -->|5 code + code_verifier| as as -->|6 access + refresh token| client client -->|7 Bearer access token| rs[Resource server]
The authorization code is a short-lived, one-time value exchanged server-to-server for tokens, so tokens never ride in the browser URL. PKCE (Proof Key for Code Exchange) binds the code to the client that started the flow: the client sends a hashed code_challenge up front and the matching code_verifier at exchange, defeating code interception. PKCE is now recommended for all clients, not just mobile.
- Scopes
- Coarse permission labels the client requests and the user consents to (e.g.,
repo:read). Scopes bound what the access token can do — they are consent, not fine-grained authorization.
This is where the abstraction starts leaking.
OAuth is authorization, not authentication. An access token says 'this app may call these APIs' — it does not reliably tell the app who the user is. Apps that treated an access token as a login built the vulnerabilities OpenID Connect was created to fix.
A client stores the refresh token in localStorage and never rotates it. An XSS bug leaks it. The attacker mints fresh access tokens indefinitely. What two changes would have contained this?
Redirect URI / token leakage
- Trigger
- Loose redirect URI matching, tokens in URLs, or non-rotating refresh tokens.
- Symptom
- Authorization codes or tokens are stolen and replayed by an attacker-controlled endpoint.
- Blast radius
- Full delegated access to the user's resources within the granted scopes.
- Mitigation
- Exact redirect URI matching, PKCE, code/refresh rotation, short TTLs, sender-constrained tokens.
Deep dive: Token Replay
OAuth delegates authority in bounded, revocable, scoped grants instead of sharing credentials. The cost is protocol complexity and a critical dependency on the authorization server. The alternative — sharing passwords or API keys — is simpler and far more dangerous.
What does PKCE protect against that the plain code flow doesn't?
Authorization code interception: only the client that generated the original code_verifier can redeem the code, so a stolen code is useless.