Concept · Observability & Security

Auth — OAuth & JWT

01

Why this matters

Every API needs to answer: who is this user, and what are they allowed to do? Roll your own auth and you'll have a CVE within months. OAuth 2.0 is how you delegate authentication to a trusted identity provider. JWTs are how you carry proof of identity between services.

Understand both, including when NOT to use JWTs as sessions (a surprisingly common mistake).

02

Authentication vs authorization

  • Authentication (authn) — "who are you?" Verifies identity. Username + password, social login, MFA.
  • Authorization (authz) — "what can you do?" Checks permissions. RBAC (role-based), ABAC (attribute-based), resource-level policies.

OAuth 2.0 is an authorization protocol, not an authentication protocol. OpenID Connect (OIDC) sits on top of OAuth to add authentication. When people say "OAuth login," they usually mean OIDC + OAuth together.

03

OAuth 2.0 authorization code flow

The modern canonical flow for web apps (with PKCE extension for public clients):

  1. User clicks "Log in with Google."
  2. App redirects browser to Google's authorization endpoint with client_id + requested scopes + redirect URI + PKCE challenge.
  3. User authenticates with Google, consents to scopes.
  4. Google redirects browser back to your app with an authorization code.
  5. Your backend exchanges the code + PKCE verifier for an access token (short-lived) and a refresh token (long-lived).
  6. Your backend uses the access token to call Google APIs, or creates its own session for the user.

Key insight: the access token never touches the browser. Client-side Single-Page Apps use PKCE to prevent code interception; public clients get the code via redirect but also need the PKCE verifier (secret only the client knows) to exchange it.

04

JWT anatomy

JSON Web Token — three base64 parts joined by dots: header.payload.signature.

HEADER    {"alg":"RS256","typ":"JWT"}
PAYLOAD   {"sub":"42","iss":"https://auth.example.com","exp":1735689600,"roles":["admin"]}
SIGNATURE RS256-signature over header + payload

Server verifies by recomputing the signature with its public key. Tampered payload → signature mismatch → reject. The payload (claims) is not encrypted — it's base64, readable by anyone. Don't put secrets in there.

Standard claims: sub (subject/user), iss (issuer), exp (expiry), aud (audience), iat (issued at). Custom claims (roles, tenant_id) are added by the issuer.

05

Stateless JWTs vs sessions in Redis

JWT-based auth

Stateless, no session store

Every request carries the JWT. Server verifies signature + expiry. No DB or Redis lookup. Catch: revoking access before expiry is hard. If a user's JWT is stolen, they keep access until it expires (usually 15 min to 1 hour).

Opaque session tokens

Random string, state in Redis

Token is an opaque ID; server looks up sessions:abc123 in Redis. Easy to revoke — just DELETE the key. Adds a Redis round-trip per request but Redis is fast. Industry default for user sessions.

Common mistake

Using JWTs as user sessions because "stateless is cool." Then trying to add revocation, which requires the very session store you tried to avoid. Moral: use JWTs for service-to-service auth and short-lived access tokens; use opaque tokens for long-lived user sessions.

06

Deep dive — refresh tokens done right

Access tokens are short (15 min). Refresh tokens are long (days/weeks/months) and used to mint new access tokens without re-asking the user.

Threats:

  • Refresh token theft. Attacker steals the refresh token, gets new access tokens indefinitely. Mitigation: refresh token rotation — every use of a refresh token returns a new one and invalidates the old. If the attacker uses it, the real user's next refresh fails and you detect the compromise.
  • Token binding. Bind tokens to device fingerprints or TLS certificates. Stolen token from another device = rejected.
  • Reuse detection. If an old (already-rotated) refresh token is used, invalidate the whole family — both the attacker and the real user are logged out. Safer to re-auth than to keep serving the attacker.

Auth0, Okta, and every modern IdP do all three by default. If you're rolling your own, follow OAuth 2.1 draft recommendations — they codify what's actually safe.

Token Lifecycle — Access + Refresh + Rotation Mermaid
stateDiagram-v2 [*] --> Issued: Login Issued --> Active: token sent to client Active --> Expired: 15 min elapsed Expired --> Refreshing: client uses refresh token Refreshing --> Active: new access + new refresh issued Refreshing --> Compromised: old refresh re-used (attacker) Compromised --> [*]: invalidate ALL family · force re-auth Active --> Revoked: explicit logout / admin action Revoked --> [*]
15 min
access token TTL
30 days
refresh token TTL
1× use
refresh token rotation
10⁻⁹
prob of HMAC forgery (HS256)
07

Real-world

Auth0 / Okta

Managed OIDC + OAuth

Identity-as-a-service. Handles social login, MFA, enterprise SSO, user management. Most SaaS companies use one of these instead of DIY auth.

AWS Cognito

AWS-native

Same pattern but integrated with other AWS services. Cheaper at scale if you're AWS-heavy; less polished UX than Auth0/Okta.

Keycloak

Open-source OIDC

Self-hosted. Full-featured. You own the DB. Used by teams with strict data residency needs.

Service mesh mTLS

Auth for service-to-service

Istio/Linkerd issue workload identities. Cert-based auth between pods. No JWTs needed inside the mesh.

08

Used in problems

E-commerce uses OAuth for login + JWTs for service-to-service. WhatsApp uses device-based auth with refresh tokens. Payment gateway uses OAuth client credentials flow for merchant API access.

Next up