Skip to main content

Command Palette

Search for a command to run...

OAuth Mistakes in Production

Updated
4 min readView as Markdown

This post is a checklist disguised as war stories

Every item below appears in real audits. OAuth did not fail — configuration and application code did.


1. Redirect URI laxity

Mistake: Register https://app.example.com/* or use open redirectors.

Why it hurts: Authorization codes go to attacker-controlled pages.

Fix:

  • Exact match only (scheme, host, port, path)

  • Separate clients for dev/stage/prod

  • No wildcard redirect URIs in production


2. Missing or weak state

Mistake: Omit state or use a static value.

Why it hurts: Login CSRF — victim links attacker's account to victim's session.

Fix: Cryptographically random state, bound to user agent session, verified on callback.


3. PKCE not enforced

Mistake: Public client without PKCE; or code_challenge_method=plain.

Why it hurts: Stolen authorization codes become tokens.

Fix: S256 PKCE for all clients; OAuth 2.1 makes this mandatory.


4. Tokens in the wrong place

Token Never Prefer
Refresh token localStorage, mobile logs Server-side store, httpOnly cookie
Access token URL query strings Authorization header
ID token Sent to third-party APIs Client login only

Mistake: SPA stores refresh token in localStorage "because it's easier."

Why it hurts: Any XSS exfiltrates long-lived credentials.


5. Trusting the ID token at the API layer

Mistake: Resource server accepts id_token as bearer token.

Why it hurts: ID token is for the client, not your orders API. Wrong audience.

Fix: APIs require access tokens with correct aud (or introspection).


6. JWT validation shortcuts

Mistake: Decode JWT, read claims, skip signature verification "in dev."

Why it hurts: alg: none and key confusion attacks.

Fix: Always verify:

  • Signature (JWKS from iss)

  • exp / nbf

  • iss matches expected realm

  • aud contains your API identifier

  • Scope for the operation

// Pseudocode — never hand-roll in production without a library
jwt.verifySignature(jwksUrl)
   .requireIssuer("https://auth.example.com/realms/myrealm")
   .requireAudience("orders-api");

7. Confusing authentication with authorization

Mistake: "User has a valid token" → full admin access.

Fix: Map IdP identity to your roles. Check permissions per resource.


8. One realm to rule them all (badly)

Mistake: Dev and prod share a realm; test users in production; admin console on public internet without IP restrict.

Fix: Environment isolation, least-privilege admin, MFA on IdP admin accounts.


9. Ignoring logout and session drift

Mistake: Clear only your app cookie; IdP SSO cookie remains.

Fix: OIDC end-session endpoint; short access token TTL; refresh rotation.


10. Logging secrets

Mistake: Log full token responses, client_secret, code_verifier.

Fix: Structured logs with token prefix only; never log secrets.


Production launch checklist

Authorization server (Keycloak / Okta / Auth0)

  • [ ] Production realm / tenant separate from dev

  • [ ] Redirect URIs exact match per environment

  • [ ] Client auth method appropriate (secret for confidential, PKCE for public)

  • [ ] Scopes are minimal (no offline_access unless needed)

  • [ ] MFA policy defined

  • [ ] Admin access restricted and audited

  • [ ] TLS everywhere; HSTS on auth domain

Application

  • [ ] state validated on every callback

  • [ ] PKCE S256 on all authorization requests

  • [ ] Refresh tokens only server-side (or BFF pattern)

  • [ ] Session cookie: Secure, HttpOnly, SameSite=Lax or Strict

  • [ ] CSRF protection on state-changing endpoints

  • [ ] Error pages do not leak codes or tokens in HTML

APIs

  • [ ] Access token validation with full claim checks

  • [ ] Authorization separate from token presence

  • [ ] Rate limiting and abuse detection

  • [ ] Clock skew tolerance configured (~60s)

Operations

  • [ ] JWKS caching with key rotation support

  • [ ] Runbook: IdP outage, key rollover, mass revocation

  • [ ] Alerts on spike in invalid_grant, invalid_client


Go deeper

Token binding and DPoP

For high-assurance APIs, consider Demonstrating Proof-of-Possession (DPoP) — binds tokens to a client key pair. Supported in some stacks; not universal yet.

Security headers for SPAs

Content-Security-Policy, frame-ancestors, no inline scripts — reduces XSS risk that makes token theft possible.

Penetration test focus areas

Ask testers to target: redirect manipulation, state bypass, ID token misuse, refresh token replay, and scope escalation.


What you should know after this post

  • [ ] You have a launch checklist copied into your project wiki

  • [ ] You know the top three browser-side mistakes (redirect, state, storage)

  • [ ] You validate JWTs completely, not cosmetically