OAuth 2.0
OAuth 2.0 is an authorization protocol, not an authentication protocol. That distinction is the single most misunderstood thing about it. OAuth answers the question "what is this app allowed to do?" not "who is this user?" It exists so you can grant a third-party application limited access to your resources on another service — without ever handing that app your password.
OAuth 2.0 Authorization Code Flow — Front/Back Channel Split
User
Client App
AuthZ Server
Resource Server
The Four Roles
Every OAuth flow involves four parties. The resource owner is you — the human who owns the data. The client is the application requesting access (a mobile app, a web app, a CLI tool). The authorization server issues tokens after verifying consent (Google's accounts.google.com, for example). The resource server hosts the actual data the client wants to access (the Google Calendar API, for instance).
These can overlap. Google runs both the authorization server and the resource server. But conceptually they're distinct, and in enterprise setups they often are separate systems.
Scopes: Limited Permissions
Scopes define what the client is allowed to do. When you see "This app wants to: Read your email, Manage your calendar," those are scopes. The client requests them, the user approves them, and the resulting token is limited to exactly those permissions.
Good scope design follows least privilege. A calendar widget should request calendar.readonly, not calendar.full_access. Overly broad scopes are a real security problem — if the token leaks, the blast radius is everything the scope allows.
Authorization Code Flow — Step by Step
This is the primary flow for web and mobile apps. Here's what happens:
- The client redirects the user's browser to the authorization server with a request that includes
client_id,redirect_uri,scope, andstate. - The user authenticates with the authorization server (logs in) and consents to the requested scopes.
- The authorization server redirects back to the client's
redirect_uriwith a short-lived authorization code. - The client's backend exchanges that code (plus
client_secret) for an access token and optionally a refresh token. - The client uses the access token to call the resource server's API.
Why the intermediate code? Why not just return the token directly? Because the redirect happens in the browser. URLs end up in browser history, server logs, referrer headers. The authorization code is single-use and short-lived, and the actual token exchange happens server-to-server where it can't be intercepted by the browser.
PKCE — Proof Key for Code Exchange
PKCE (pronounced "pixie") closes a gap in the authorization code flow for public clients — apps that can't keep a client_secret secret, like mobile apps and SPAs.
Before the flow starts, the client generates a random code_verifier and derives a code_challenge from it (a SHA-256 hash). The challenge goes in the initial authorization request. When the client exchanges the code for a token, it sends the original code_verifier. The authorization server hashes it and compares. If an attacker intercepts the authorization code, they can't exchange it without the verifier.
PKCE is now recommended for all clients, not just public ones. It's essentially free protection against code interception.
The State Parameter — CSRF Protection
The state parameter is a random, unguessable value the client generates and includes in the authorization request. When the authorization server redirects back, it includes the same state. The client verifies it matches. Without this, an attacker could craft a malicious authorization URL and trick a user into linking the attacker's account — a CSRF attack on the OAuth flow itself.
Missing state validation is one of the most common OAuth implementation bugs.
Flows You Should Know About
Implicit flow returned the token directly in the URL fragment. It's deprecated. The token was exposed in browser history and vulnerable to interception. Use Authorization Code + PKCE instead.
Client Credentials flow is for machine-to-machine communication where there's no user involved. A backend service authenticates with client_id and client_secret and gets an access token directly. No browser, no redirects, no user consent. This is how your payment service talks to your billing service.
Refresh Tokens and Rotation
Access tokens are short-lived — minutes to hours. Refresh tokens are long-lived and let the client get new access tokens without sending the user back through the consent flow.
Refresh token rotation means each time you use a refresh token, the authorization server invalidates it and issues a new one along with the new access token. If an attacker steals a refresh token, the next legitimate use by either party triggers detection — the server sees a reused token and revokes the entire grant.
OpenID Connect — The Authentication Layer
OAuth alone doesn't tell the client who the user is. OpenID Connect (OIDC) is a thin layer built on top of OAuth 2.0 that adds authentication. It introduces the id_token, a JWT that contains claims about the user — their subject identifier, email, name, and when they authenticated.
When you click "Sign in with Google," you're using OIDC, not raw OAuth. The client gets an id_token that says "this is user 12345, they authenticated 30 seconds ago with MFA," plus an access token if it also needs to call Google APIs on the user's behalf.
The openid scope triggers OIDC behavior. Additional scopes like profile and email control which claims appear in the id_token.
Common Vulnerabilities
Open redirect on redirect_uri. If the authorization server doesn't strictly validate the redirect URI, an attacker can register a malicious URI and steal the authorization code. Exact-match validation is required — no wildcards, no partial matches.
Missing state parameter. Enables CSRF attacks that link a victim's account to the attacker's OAuth grant.
Leaked client secrets in mobile apps. Client secrets embedded in mobile binaries are extractable. This is exactly why PKCE exists — don't rely on a secret that isn't secret.
Overly broad scopes. An app that requests admin:everything when it only needs read:profile creates unnecessary risk. If the token leaks, the attacker gets admin access.
Token storage. Access tokens stored in localStorage are vulnerable to XSS. HttpOnly cookies or secure storage APIs are safer.
Key Takeaways
- OAuth 2.0 is authorization (what can this app do?), not authentication (who is the user?). OpenID Connect adds the authentication layer on top.
- The Authorization Code flow uses an intermediate code so the actual token never appears in browser URLs, history, or logs.
- PKCE protects the code exchange from interception and is now recommended for all client types, not just mobile apps.
- The
stateparameter is CSRF protection for the OAuth flow itself — never skip it. - Refresh token rotation provides detection of stolen tokens by invalidating tokens on each use.
- Most OAuth vulnerabilities come from loose redirect_uri validation, missing state, and leaked secrets — implementation bugs, not protocol flaws.
Next up: Zero Trust Architecture — why the network perimeter is dead and identity became the new security boundary.
Enjoyed this breakdown?
Get new lessons in your inbox.