Intermediate22 min

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.

How deep?
How the pieces actually move.

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, the whole system

First, see the whole system. Then we’ll open it up.

Authorization Code flow with PKCE

Authorization Code flow with PKCE

  1. The client redirects the user to the authorization server with a PKCE code challenge.
  2. The user authenticates and consents; the server returns a short-lived authorization code.
  3. The client exchanges the code plus its PKCE verifier for access and refresh tokens.
  4. 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.
Abstraction leak

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.

Now break it

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

Authority

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.

OAuth gets you an access token to call APIs. But how does the app learn WHO the user is? OAuth alone doesn't tell you.

Next: OpenID Connect →