OAuth is everywhere and most developers use it without really understanding what's happening under the hood. You click "Sign in with Google," magic happens, and you're logged in. But when something breaks — a token expires, a redirect fails, a scope is wrong — you're suddenly debugging a protocol you never learned.
I built OAuth integrations for years before I actually understood the full flow. Here's what I wish someone had explained from the start.
The Four Actors
OAuth has four players, and mixing them up is where most confusion starts:
- Resource Owner — that's you, the user. You own the data.
- Client — the app requesting access to your data. Could be a web app, mobile app, or CLI tool.
- Authorization Server — issues tokens after you grant permission. Google, GitHub, Auth0 — these run authorization servers.
- Resource Server — the API that holds your actual data. Sometimes this is the same company as the auth server, sometimes not.
The whole point of OAuth: the Client gets limited access to the Resource Server without ever seeing your password.
The Authorization Code Flow (Step by Step)
This is the most common OAuth flow, and the one you should use for almost everything:
1. The app redirects you to the authorization server. The URL includes the client ID, requested scopes (what permissions it wants), a redirect URI (where to send you back), and a state parameter (CSRF protection).
2. You log in and consent. The authorization server shows you what the app is asking for — "Acme App wants to read your email and profile." You say yes or no.
3. The auth server redirects back with a code. Not a token — a short-lived authorization code. This is a critical distinction. The code is useless on its own.
4. The app exchanges the code for tokens. This happens server-to-server (backend to auth server), using the authorization code plus the client secret. The response includes an access token and usually a refresh token.
5. The app uses the access token to call the API. The token goes in the Authorization header. The resource server validates it and returns data.
Why not just return the token directly in step 3? Because the redirect happens in the browser — it's visible in the URL bar, browser history, and server logs. The code-for-token exchange happens server-side where it's actually secure.
PKCE: Fixing the Gap for Public Clients
The authorization code flow has a weakness: what if the client can't keep a secret? Mobile apps and single-page apps ship their entire source code to the user. There's no safe place to store a client secret.
PKCE (pronounced "pixie") — Proof Key for Code Exchange — fixes this. Here's how:
Before the auth request, the client generates a random string called a code_verifier and computes its SHA-256 hash, called the code_challenge.
During the auth request, the client sends the code_challenge to the authorization server.
During the token exchange, the client sends the original code_verifier. The auth server hashes it and checks that it matches the challenge from earlier.
An attacker who intercepts the authorization code can't use it — they don't have the original code_verifier. The math is one-directional: you can go from verifier to challenge, but not back.
OAuth 2.1 makes PKCE mandatory for all clients, not just public ones. That tells you how important the security community considers it.
Access Tokens vs Refresh Tokens
Access tokens are short-lived (minutes to hours) and used for API calls. When they expire, the app uses the refresh token to get a new one without making the user log in again.
Refresh tokens are long-lived and powerful — losing one is like losing a session. Best practice: rotate refresh tokens on every use (one-time-use tokens) and store them securely.
OAuth vs OpenID Connect
OAuth handles authorization — "can this app access my photos?" OpenID Connect (OIDC) adds authentication on top — "who is this user?" OIDC introduces the ID token, which contains user identity claims (name, email, etc.) in a JWT format.
If you need "Sign in with X" — that's OIDC. If you need "let this app read my calendar" — that's OAuth. In practice, most implementations use both together.
Common Mistakes I've Seen
Storing tokens in localStorage. Accessible to any JavaScript on the page, including XSS attacks. Use httpOnly cookies or in-memory storage instead.
Overly broad scopes. Request the minimum permissions you need. If you only read emails, don't ask for write access.
Not validating the state parameter. This is your CSRF protection. Skip it and attackers can trick users into linking their account to the attacker's OAuth session.
Treating access tokens as proof of identity. An access token says "this request is authorized." It doesn't reliably tell you who made it. Use OIDC ID tokens for identity.
The Bottom Line
OAuth looks complicated because it solves a genuinely hard problem: delegated access without shared credentials, across untrusted networks, for multiple client types. Once you see the reasoning behind each step — why there's a code exchange, why PKCE exists, why tokens are split into access and refresh — the protocol makes a lot of sense.
The video above walks through each step visually. If you're implementing OAuth from scratch, I'd recommend watching it alongside the official RFC 6749 and Auth0's docs. Theory plus implementation examples is the fastest way to get this stuff to click.
Building something with OAuth right now? Hit me up in the comments — happy to help debug.
Top comments (0)