OAuth in Plain English
07 May 2026OAuth’s spec reads like it was written to be cited, not understood. The actual mechanism — the authorization code flow with PKCE, which is what you should be implementing in 2026 for any client that isn’t a trusted backend — is simpler than it looks. Here’s what’s happening at each step, without the RFC language.
What problem this solves
You want to let a user log in with Google, or let your app access a user’s GitHub repos, without ever seeing their Google or GitHub password. OAuth is how a third party (Google) vouches for a user to your app, and hands your app a token scoped to only what you asked for.
The flow, step by step
Five parties’ worth of back-and-forth, but only two hops ever cross the open internet with anything sensitive in them:
sequenceDiagram
participant U as User
participant A as Your App
participant G as Google
U->>A: Clicks "Log in with Google"
A->>U: Redirect to Google (code_challenge, state)
U->>G: Approves, on Google's own page
G->>A: Redirect back with code + state
A->>G: Exchange code + code_verifier (server-to-server)
G->>A: access_token, id_token, refresh_token
1. Your app redirects the user to the provider.
https://accounts.google.com/o/oauth2/v2/auth?
client_id=your-client-id&
redirect_uri=https://yourapp.com/callback&
response_type=code&
scope=email profile&
code_challenge=BASE64URL(SHA256(code_verifier))&
code_challenge_method=S256&
state=random-csrf-token
code_verifier is a random string your app generated and kept secret, client-side. code_challenge is its hash, sent openly in this URL. This pair is PKCE (Proof Key for Code Exchange) — it’s what stops a stolen authorization code from being usable by anyone but the client that started this exact flow.
2. The user logs in and approves, on Google’s site, not yours.
This is the actual security property OAuth buys you: your app’s code never touches the user’s Google password. Only Google’s login page does.
3. Google redirects back with a short-lived code.
https://yourapp.com/callback?code=4/0AY0e-g6...&state=random-csrf-token
Check that state matches what you sent in step 1 — this is your CSRF protection, confirming this callback is answering the request you actually made, not one an attacker injected.
4. Your backend exchanges the code for tokens.
This step happens server-to-server, never in the browser:
curl -X POST https://oauth2.googleapis.com/token \
-d client_id=your-client-id \
-d client_secret=your-client-secret \
-d code=4/0AY0e-g6... \
-d code_verifier=the-original-random-string \
-d grant_type=authorization_code \
-d redirect_uri=https://yourapp.com/callback
code_verifier proves you’re the same client that generated the code_challenge in step 1 — this is what makes the authorization code useless if intercepted in the browser redirect, since an attacker who steals the code doesn’t have the verifier.
5. Google responds with tokens.
{
"access_token": "ya29.a0AfH6...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"expires_in": 3599,
"refresh_token": "1//0gW..."
}
access_token: what you send to Google’s APIs on the user’s behalf. id_token: a JWT with the user’s identity claims — this is what you decode to get their email and confirm who they are. refresh_token: use it to get a new access token when this one expires, without another full login.
Why PKCE, specifically, in 2026
PKCE used to be a mobile/SPA-only addition on top of the base authorization code flow, for clients that can’t safely hold a client_secret. It’s now recommended for every client type, backend included, because it closes the authorization code interception attack regardless of client trust level, and there’s no real cost to including it.
The mistake that breaks this
Skipping the state parameter check on the callback. Without it, an attacker can trick a victim into completing an OAuth flow initiated by the attacker, linking the victim’s session to the attacker’s account — a real, exploitable class of bug, not theoretical.
The concrete next action
If you’re hand-rolling this, use a maintained library (openid-client in Node, authlib in Python) rather than implementing the token exchange yourself. The flow above is what to understand; the actual signature verification and edge cases are where hand-rolled implementations get subtly wrong.