OAuth 2.0 & JWT
OAuth2 grant types, JWT structure and validation, access tokens vs refresh tokens, token storage, and common security pitfalls.
Why Authentication Is Hard at Scale
Authentication — confirming who a user is — seems simple in a monolith with a single session store. Once you have microservices, mobile clients, third-party integrations, and millions of users, the problem explodes in complexity. You need a protocol that is stateless (so any server can validate a token without calling a central store), delegatable (so a user can grant limited access to a third-party app), and revocable (so stolen credentials can be invalidated quickly). OAuth 2.0 and JWTs together address these requirements, which is why they appear in nearly every system design interview involving user data.
OAuth 2.0: The Protocol
OAuth 2.0 is an authorization framework, not an authentication protocol. It allows a Resource Owner (user) to grant a Client (your app) limited access to resources stored on a Resource Server (e.g., Google Drive) without sharing credentials. An Authorization Server (e.g., Auth0, Google Identity) issues tokens. OpenID Connect (OIDC) sits on top of OAuth 2.0 to add authentication — it introduces the `id_token` (a JWT describing the user) on top of the access token.
| Grant Type | Who Uses It | Has User Interaction | Recommended |
|---|---|---|---|
| Authorization Code + PKCE | Web apps, SPAs, Mobile | Yes | Yes — current best practice |
| Client Credentials | Server-to-server (M2M) | No | Yes — for backend services |
| Device Code | Smart TVs, CLIs, IoT | Yes (secondary device) | Yes — for constrained devices |
| Implicit | SPAs (legacy) | Yes | No — deprecated, token exposed in URL |
| Resource Owner Password | First-party trusted clients | No (password sent) | No — avoid, defeats OAuth purpose |
Authorization Code Flow with PKCE
JWT: Structure and Validation
A JSON Web Token (JWT) is a compact, URL-safe token format that encodes claims as a signed JSON object. It has three Base64URL-encoded segments separated by dots: `header.payload.signature`. The header specifies the signing algorithm (`alg`). The payload contains claims. The signature proves the token was issued by a trusted party and was not tampered with.
// Header (decoded)
{
"alg": "RS256", // RSA signature with SHA-256
"typ": "JWT",
"kid": "key-id-1" // Key ID for rotation
}
// Payload (decoded)
{
"iss": "https://auth.example.com", // Issuer
"sub": "user_12345", // Subject (user ID)
"aud": "api.example.com", // Audience
"exp": 1740000000, // Expiry (Unix timestamp)
"iat": 1739996400, // Issued at
"jti": "unique-token-id", // JWT ID (for blacklisting)
"roles": ["viewer", "commenter"], // Custom claims
"plan": "pro"
}
// Signature = RS256(base64url(header) + "." + base64url(payload), privateKey)JWTs Are Signed, Not Encrypted
The payload of a standard JWT (JWS — JSON Web Signature) is only Base64URL-encoded, NOT encrypted. Anyone who obtains the token can decode and read the claims. Never store sensitive data (passwords, PII, payment info) in a JWT payload. For confidential payloads, use JWE (JSON Web Encryption) instead.
Validation Checklist
- Verify signature using the public key (for RS256) or shared secret (for HS256). Reject if invalid.
- Check `alg` header — reject `alg: none` or unexpected algorithms (algorithm confusion attacks).
- Validate `exp` claim — reject if token is expired.
- Validate `iss` (issuer) matches your expected authorization server.
- Validate `aud` (audience) contains your service's identifier.
- Optionally check `jti` against a token blacklist if revocation is needed.
Access Tokens vs Refresh Tokens
| Property | Access Token | Refresh Token |
|---|---|---|
| Lifetime | Short (5–60 minutes) | Long (days to months) |
| Sent to | Resource Server (APIs) | Authorization Server only |
| Storage | Memory or HttpOnly cookie | Secure HttpOnly cookie |
| Revocable | Not easily (stateless) | Yes — stored in Auth Server DB |
| Purpose | Proves authorization for API calls | Obtains new access tokens |
| If stolen | Attacker has limited window | Attacker can generate tokens indefinitely |
The standard pattern is: issue a short-lived access token (15–60 minutes) for API access and a long-lived refresh token (7–30 days) stored in an HttpOnly cookie. When the access token expires, the client silently exchanges the refresh token for a new pair. This limits exposure if an access token is stolen while keeping users logged in without re-authentication.
Token Storage Security
| Storage Location | XSS Risk | CSRF Risk | Recommendation |
|---|---|---|---|
| localStorage / sessionStorage | HIGH — JS can read it | Low | Avoid for sensitive tokens |
| In-memory (JS variable) | Medium — lost on refresh | Low | Good for short-lived access tokens |
| HttpOnly Cookie | Low — JS cannot read it | HIGH — sent automatically | Use with CSRF tokens or SameSite=Strict |
| HttpOnly + SameSite=Strict Cookie | Low | Low | Best practice for refresh tokens |
Interview Tip
In interviews, when asked about auth, lead with the threat model. Ask: 'Is this a mobile app, SPA, or server-rendered app?' SPAs should use Authorization Code + PKCE, store refresh tokens in HttpOnly cookies, and access tokens in memory. Mention the XSS vs CSRF trade-off with cookie storage — showing you understand both attack vectors impresses interviewers.
Common Pitfalls and Attacks
- JWT Algorithm Confusion: Attacker changes `alg` from `RS256` to `HS256` and signs with the public key (which they have). Always pin the expected algorithm server-side.
- Open Redirect in Redirect URI: OAuth redirects to attacker-controlled URL if `redirect_uri` is not strictly validated. Always whitelist exact redirect URIs.
- CSRF on Authorization Endpoint: Without a `state` parameter, attacker can initiate OAuth flow on victim's behalf. Always validate the `state` parameter to prevent CSRF.
- Token Leakage via Referer Header: Access tokens in URL query params (like Implicit flow) leak through `Referer` headers. Never put tokens in URLs.
- Refresh Token Theft: A compromised refresh token grants indefinite access. Use refresh token rotation (issue new refresh token on each use, invalidate old one) and detect reuse as a breach signal.
Use an Identity Provider in Production
Building OAuth 2.0 from scratch is error-prone and risky. Use a managed identity provider: Auth0, Okta, AWS Cognito, Google Identity Platform, or Keycloak (self-hosted). They handle token storage, refresh rotation, MFA, social login, and compliance (SOC2, GDPR) out of the box. In interviews, mention this and explain what your custom solution would need to replicate.