OAuth 2.0 and JWT

OAuth 2.0 lets one app access another on behalf of a user without sharing passwords. JWT is a self-contained token format. They are often used together but solve different problems. Let's cut through the confusion.

What problem OAuth solves

You install an app that wants to read your Google contacts. The wrong way is to type your Google password into the app. Now the app has full access to your email, your photos, your everything, forever. The right way is OAuth. The app redirects you to Google, you log in with Google directly, you grant specific permissions ("read contacts only"), and Google hands the app a token that only works for that one purpose.

OAuth 2.0 is the standard for delegated authorization. The pattern is everywhere: "Sign in with Google", "Connect your Slack", "Authorize Stripe", any third-party integration on the modern web.

The four roles

The Authorization Code flow

This is the main flow you should know. It is what every web app uses.

OAuth 2.0 Authorization Code Flow User App (Client) Auth Server Resource Server 1. Click "Connect Google" 2. Redirect with client_id, scope 3. Login + consent screen 4. Redirect to app with ?code=xyz 5. POST code + client_secret 6. Access token + refresh token 7. GET /api with Authorization: Bearer <token> 8. Data

The reason for the two-step (code first, then exchange for token) is that the code travels through the user's browser where it might be intercepted, but the token never does. The token exchange happens server-to-server with the client_secret, which proves the app is who it says it is.

Refresh tokens

Access tokens are short-lived (usually 15 minutes to 1 hour) so that if one leaks, the damage window is small. Refresh tokens are long-lived (days to months) and used only to get new access tokens. Store refresh tokens carefully — they are essentially long-term credentials.

What is a JWT

JSON Web Token. A self-contained token format. Three base64-encoded parts separated by dots: header, payload, signature.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsImV4cCI6MTcxNDk5OTk5OX0.signature

Decoded payload looks like:

{"sub": "42", "exp": 1714999999, "role": "admin"}

The signature is computed over the header and payload using either a shared secret (HS256) or an asymmetric key pair (RS256). Anyone can read a JWT (it is just base64), but only someone with the signing key can produce a valid one.

JWTs are popular because they are stateless. The server does not need to look up a session. It just verifies the signature, reads the claims, and trusts them. This scales horizontally because any server can validate any token.

The downside is revocation. With a session in a database, you can delete it and the user is logged out instantly. With a JWT, the token is valid until it expires, no matter what. The standard mitigations are short expiry plus refresh tokens, or maintaining a denylist of revoked JWTs (which sort of defeats the stateless benefit). For high-security systems, opaque tokens with a database lookup are still the safer choice.

Common mistake: Storing JWTs in localStorage. They are vulnerable to XSS. Prefer httpOnly secure cookies for browser apps. Even better, follow the BFF (backend-for-frontend) pattern where the JWT lives only on the server and the browser holds an opaque session cookie.