OAuth & JWT
So far identity has lived inside one system. The moment a third party is involved — “let this app read my calendar,” “log in with Google” — you face a new problem: how do you grant someone else limited access to your account without handing them your password? OAuth answers the delegation question; JWT answers the “how do we carry proof around” question. They’re often used together but they solve different problems, and conflating them causes endless confusion.
OAuth2: delegation, not login
Section titled “OAuth2: delegation, not login”OAuth2 is a framework for delegated authorization. Its founding insight is that giving an app your password is catastrophic — the app could do anything you can, forever, and you couldn’t revoke it without changing your password everywhere. OAuth replaces that with a scoped, revocable access token issued by the service that holds your account.
The four roles:
RESOURCE OWNER → you (the human) CLIENT → the app wanting access (e.g. a photo printer) AUTHORIZATION SERVER → the one that authenticates you & issues tokens (e.g. Google) RESOURCE SERVER → the API holding your data (e.g. Google Photos)The canonical Authorization Code flow:
1. Client sends you to the Auth Server: "this app wants read access to your photos"2. You authenticate THERE (the client never sees your password) and consent3. Auth Server redirects back to the client with a short-lived AUTHORIZATION CODE4. Client exchanges that code (plus its own secret) for an ACCESS TOKEN — server-to-server5. Client calls the Resource Server with the access token; it returns only the scoped dataThe genius is the scope (“read photos” only) and the separation: your password stays with the party you already trust, and the client gets a narrow, expirable key instead. That’s least privilege applied to delegation.
JWT: a token you can verify without asking
Section titled “JWT: a token you can verify without asking”A JSON Web Token is a compact, signed, self-contained claim. “Self-contained” is the key word: the receiver can verify it using only a key, without calling back to whoever issued it. It has three base64url parts joined by dots:
header . payload . signature ─────── ─────── ───────── {alg,typ} {claims} sign(header.payload, key)
header : which signing algorithm payload: the claims — sub (subject), exp (expiry), iss (issuer), scopes, roles… signature: proves the first two parts weren't altered AND came from the issuerThe signature is everything. Anyone can read a JWT (it is not encrypted — only signed), but only the holder of the key can produce a valid one. Change one byte of the payload and the signature no longer matches, so a tampered token is rejected. This is what lets any server verify a token with just the public key — no shared session store, no database lookup.
Access tokens vs. refresh tokens
Section titled “Access tokens vs. refresh tokens”Tokens force a brutal trade-off. A short lifetime limits the damage of a stolen token; a long lifetime spares users from re-logging-in constantly. You can’t have both with one token — so you use two:
ACCESS TOKEN → short-lived (minutes), sent on every API call. If stolen, it expires fast. Self-verifiable (often a JWT). REFRESH TOKEN → long-lived (days/weeks), sent ONLY to the auth server to mint fresh access tokens. Kept secret; never hits APIs.The access token is the disposable day-pass; the refresh token is the harder-to-steal master key you present only at one well-guarded door. This split is how systems get both convenience and a small blast radius.
The statelessness benefit and the revocation pitfall
Section titled “The statelessness benefit and the revocation pitfall”Here is the whole trade, stated plainly — and it directly continues the sessions-vs-tokens story from Authentication and the scaling story in Statelessness & Sessions.
What statelessness buys us: any server can verify a JWT with just a key — no shared session store, no per-request database lookup. This is a huge win for horizontal scaling and for multi-service architectures, where a token minted by the auth service is trusted by every downstream service independently.
What it costs us: you can’t easily un-issue a self-contained token. A session can be revoked by deleting one row; a valid JWT is valid until it expires, even if you fire the employee or detect the theft thirty seconds after issuing it. The mitigations all claw back some statelessness:
short expiry → limits the window, but never to zero token blocklist → check a denylist of revoked IDs (a lookup — partly stateful again) token versioning → bump a per-user counter; tokens with old versions fail rotate signing key → nuclear option: invalidates EVERY token at onceThere is no perfectly clean answer. You are trading instant revocation for stateless scale, and the right balance — short access tokens plus a refresh mechanism, occasionally backed by a denylist for high-stakes actions — is exactly the kind of deliberate trade this whole part is about.
Under the hood — how a server finds the right key (kid + JWKS)
Section titled “Under the hood — how a server finds the right key (kid + JWKS)”“Verify the signature with the issuer’s key” raises a practical question: which key? Issuers rotate
signing keys and often run several at once, so a JWT’s header carries a kid (key ID) naming the
key that signed it. The verifier fetches the issuer’s public keys from a well-known JWKS (JSON Web
Key Set) endpoint — a URL publishing the current public keys as JSON — caches them, and picks the one
whose kid matches.
This is what makes stateless verification and key rotation coexist. The auth server can introduce a
new key, start signing with it, and keep publishing the old public key until the last token signed with
it expires — no downtime, no shared session store. It also sharpens the warning above: pin the expected
algorithm server-side and only trust keys from the issuer’s real JWKS. Letting the token choose its
own algorithm or its own key source is how the alg: none and key-confusion attacks get their foot
in the door.
The thread
Section titled “The thread”OAuth lets a user delegate scoped access without surrendering their password; OIDC adds a proper login on top; JWTs are the signed, self-verifying envelope that carries identity and permissions across trust boundaries without a central lookup. Together they let independent systems trust each other’s claims — buying enormous scale at the price of harder revocation. Whatever crosses these boundaries must also be protected on the wire and at rest, which is exactly Encryption in Transit & at Rest →.
The architect’s lens
Section titled “The architect’s lens”OAuth and JWT carry identity across systems — adopt them through the five questions:
- Why do they exist? Because giving a third-party app your password is catastrophic — it could do anything you can, forever, unrevocable without changing it everywhere. OAuth replaces that with a scoped, expirable token; JWT is the self-verifying envelope that carries the proof.
- What problem do they solve? OAuth2 = delegated authorization (a “read photos only” token, password stays with the party you trust); OIDC adds login on top; a JWT lets any server verify identity with just a key — no central session store, no per-request lookup — which is the win for horizontal scale and multi-service trust.
- What are the trade-offs? The whole bargain is stateless scale for harder revocation: a self-contained JWT is valid until it expires even after you fire the employee. The mitigations (short expiry, denylist, token versioning, key rotation) all claw back some statelessness. Hence access/refresh token split: a disposable minutes-long day-pass plus a well-guarded master key.
- When should I avoid it? Use OAuth2 to access an API, OIDC to log a user in — bolting login onto bare OAuth2 is a classic subtle bug. Avoid long-lived JWTs for high-stakes actions where instant revocation matters; reach for a session there instead.
- What breaks if I remove it (or implement it naively)? Without delegation you’re back to password-sharing; without JWT every service needs a shared session lookup. And the signature is everything: honor
alg: noneor let the token pick its own verification and an attacker forges a valid token (the 2015 JWT library breaks) — never let the thing you verify tell you how to verify it.
Check your understanding
Section titled “Check your understanding”- What problem does OAuth2 solve that “just give the app your password” creates?
- Distinguish OAuth2 from OIDC — which one is for logging a user in?
- A JWT is signed but not encrypted. What can an attacker do with it, and what can’t they?
- Why split authority into a short access token and a long refresh token instead of one token?
- State the statelessness benefit of JWTs and the revocation pitfall, plus one way to mitigate it.
Show answers
- Giving an app your password is catastrophic — it could do anything you can, forever, and you couldn’t revoke it without changing your password everywhere. OAuth2 solves this with delegated authorization: a scoped, revocable, expirable access token issued by the service that holds your account, so your password stays with the party you already trust and the app gets a narrow key instead.
- OAuth2 grants access to an API (delegated authorization); it was never designed to prove who you are. OIDC (OpenID Connect) is a thin layer on top that adds an ID token (a JWT describing the authenticated user) for logging a user in. “Sign in with Google” is OIDC.
- A JWT is signed, not encrypted, so an attacker can read it (the payload is just base64url — never put secrets in it) but cannot forge a valid one without the signing key. Change one byte and the signature no longer matches, so a tampered token is rejected — which is what lets any server verify it with just a key, no lookup.
- One token can’t be both safe-if-stolen and convenient. A short access token (minutes, sent on every API call) limits the damage of theft because it expires fast; a long refresh token (days/weeks, sent only to the auth server to mint new access tokens) spares users from constant re-login. The split gives both convenience and a small blast radius — a disposable day-pass plus a well-guarded master key.
- Benefit: any server can verify a JWT with just a key — no shared session store, no per-request lookup — a huge win for horizontal scale and multi-service architectures. Pitfall: you can’t easily un-issue a self-contained token; it stays valid until expiry even after you fire the employee or detect the theft. Mitigations include short expiry, a token blocklist/denylist of revoked IDs, token versioning, or rotating the signing key (the nuclear option) — each clawing back some statelessness.