Skip to content

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 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 consent
3. Auth Server redirects back to the client with a short-lived AUTHORIZATION CODE
4. Client exchanges that code (plus its own secret) for an ACCESS TOKEN — server-to-server
5. Client calls the Resource Server with the access token; it returns only the scoped data

The 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 issuer

The 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.

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 once

There 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.

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 →.

  1. What problem does OAuth2 solve that “just give the app your password” creates?
  2. Distinguish OAuth2 from OIDC — which one is for logging a user in?
  3. A JWT is signed but not encrypted. What can an attacker do with it, and what can’t they?
  4. Why split authority into a short access token and a long refresh token instead of one token?
  5. State the statelessness benefit of JWTs and the revocation pitfall, plus one way to mitigate it.