"OAuth2 was designed in 2012 for confidential server-side clients with secrets. The world filled up with mobile apps and SPAs that can't keep secrets. PKCE fixed that by adding a per-request proof. In 2026, every flow — confidential or public — uses PKCE."
What OAuth2 Actually Is
OAuth2 is a delegated authorization protocol. The user grants a third-party app permission to access resources on their behalf, without ever sharing their password with the third party. The classic example: "Allow this app to read your Google Calendar." You authenticate with Google directly; Google issues a scoped token to the app; the app uses the token to access your calendar.
Four roles:
- Resource owner — you, the user.
- Client — the third-party app requesting access.
- Authorization server — the identity provider (Google, GitHub, Auth0, Anthropic) that authenticates the user and issues tokens.
- Resource server — the API holding the protected resources (Google Calendar API, GitHub API). Often the same organization as the auth server.
Why the Authorization Code Flow Exists
The flow's job: get a token from the auth server to the client, without exposing the token to the user's browser address bar or browser history (where it could leak).
- Client redirects the user's browser to the auth server with
?response_type=code&client_id=...&redirect_uri=...&scope=...&state=.... - User authenticates with the auth server (login, MFA, whatever), reviews scopes, consents.
- Auth server redirects back to
redirect_uri?code=...&state=.... - Client's BACKEND takes that code, POSTs it to the auth server's token endpoint with its client_secret, receives an
access_token(and usually arefresh_token). - Client uses the access_token on resource server requests as
Authorization: Bearer ....
The intermediate code is a one-time, short-lived placeholder that's safe to send through the browser. The actual token never touches the browser address bar.
The Problem with Mobile and SPA Clients
Step 4 above requires the client_secret — the value that proves the request is coming from the legitimate client app, not an impostor. Server-side apps store it in env vars (safe). Mobile apps embed it in the binary (anyone with the binary can extract it). SPAs ship it in JavaScript (anyone with the URL can extract it).
Historically, the OAuth2 spec offered the Implicit Flow as a workaround: skip the code exchange entirely, return tokens directly in the redirect URI fragment. But this leaked tokens through browser history, referrer headers, and didn't support refresh tokens. It's been retired.
How PKCE Fixes It
PKCE (Proof Key for Code Exchange, RFC 7636) adds a per-request secret that doesn't need to be pre-shared:
- BEFORE the redirect, client generates a random
code_verifier(43-128 chars) and computescode_challenge = base64url(sha256(code_verifier)). - Client sends the
code_challengein the initial authorization request:?response_type=code&client_id=...&code_challenge=...&code_challenge_method=S256. - Auth server stores the challenge alongside the issued code.
- When the client exchanges code for token, it sends the original
code_verifieralong. - Auth server checks
sha256(code_verifier) == code_challenge. If yes, issues the token. If no, rejects.
The win: an attacker who intercepts the authorization code can't exchange it without knowing the code_verifier — which only the original client knows because they generated it locally. No client_secret required.
Scopes — Granular Permission Grants
The scope parameter lists what the client is asking for: scope=read:calendar write:calendar.events. The auth server shows these to the user during consent ("This app wants to: read your calendar, create calendar events"). Granted scopes become claims on the issued token; resource servers enforce them when the token is used.
Scope design is API-specific. Google APIs use URL-style scopes (https://www.googleapis.com/auth/calendar.readonly); GitHub uses short names (repo, user:email); Stripe doesn't use OAuth scopes much because most integrations are full-account. Pick a convention; document it.
OIDC — Adding Identity to OAuth2
OAuth2 is authorization, not authentication. "This token has permission to read calendar" doesn't tell you WHO the user is. OpenID Connect (OIDC) sits on top of OAuth2 and adds an id_token (a signed JWT containing the user's identity claims) to the response. Most modern "Sign in with X" buttons (Google, Apple, Microsoft) implement OIDC, not raw OAuth2. The flow shape is identical; OIDC just adds the id_token.