JWT vs Session / OAuth
Don't confuse token format, session model, and authorization protocol.
Problem it solves
- After login, the server must recognize returning users without re-entering credentials every request.
- Teams confuse JWT (token format), session (server-side state model), and OAuth (delegation protocol) — leading to wrong security decisions.
- Microservices need a consistent way to propagate identity and permissions across service boundaries.
Definition
Session auth stores user state server-side; the client holds only a session ID (usually in an HttpOnly cookie). JWT is a signed, self-contained token format carrying claims. OAuth 2.0 is a protocol for delegated authorization (app accessing resources on behalf of a user). OIDC (OpenID Connect) extends OAuth with an ID token for authentication.
Why It Matters
- Wrong model choice affects revocation, scaling, and security posture.
- JWT statelessness simplifies horizontal scaling but complicates logout and token invalidation.
- OAuth enables "Login with Google" without storing user passwords — but scopes and consent matter.
- Interviewers test whether you understand these are orthogonal concepts that can be combined.
Visual
Session: server stores session state client sends session cookie (HttpOnly) JWT: token carries signed claims server verifies signature (stateless) OAuth 2.0: delegated authorization framework (often issues JWT access tokens via OIDC) OIDC: identity layer on OAuth adds ID token (who the user is)
When to Use
- Session + cookie for traditional server-rendered web apps needing instant server-side revocation.
- JWT access token for stateless API auth across microservices (short-lived, signed).
- Refresh token + access token rotation for SPA/mobile when you need both statelessness and revocable long sessions.
- OAuth 2.0 / OIDC for third-party login (Google, GitHub) and delegated API access with scoped permissions.
When NOT to Use
- Long-lived JWTs (hours/days) without rotation, blacklist, or introspection strategy.
- Storing JWT in
localStorage— XSS can steal it; prefer HttpOnly Secure cookies or memory + refresh flow. - Putting sensitive PII or permissions that change frequently inside JWT claims without short TTL.
- Calling OAuth "authentication" without OIDC ID token — OAuth alone is authorization, not identity proof.
Code Variations
Claims claims = Jwts.parser()
.verifyWith(publicKey)
.build()
.parseSignedClaims(token)
.getPayload();
String userId = claims.getSubject();
if (claims.getExpiration().before(new Date())) {
throw new TokenExpiredException();
}iss) / audience (aud).String sessionId = request.getCookie("SESSION");
Session session = sessionStore.find(sessionId)
.orElseThrow(UnauthorizedException::new);
User user = session.getUser();JWT:
- Short access token TTL (5–15 min)
- Refresh token rotation
- Never store in localStorage (XSS risk)
- HttpOnly Secure cookie OR memory-only
Session:
- HttpOnly + Secure + SameSite cookies
- CSRF token for state-changing requests
- Regenerate session ID on login (fixation)
OAuth:
- Validate redirect URI exactly
- Use PKCE for public clients
- Request minimum scopes neededSession vs JWT vs OAuth vs OIDC
| Aspect | Session | JWT | OAuth 2.0 | OIDC |
|---|---|---|---|---|
| What it is | Stateful login context on server | Signed token format (claims + signature) | Delegated authorization protocol | Identity layer on OAuth (authn) |
| State location | Server (session store/Redis) | Client carries token; server verifies signature | Authorization server holds grants | Same + ID token with user claims |
| Revocation | Easy — delete session server-side | Hard — need short TTL, blacklist, or introspection | Revoke refresh token / grant | Revoke tokens + end session at IdP |
| Scaling | Needs shared session store for multi-node | Stateless verification — scales horizontally | Central auth server | Central identity provider |
| Best fit | Web app login, admin panels | Service-to-service API auth | 3rd-party API access, scoped delegation | Social login, SSO, federated identity |
| Common pitfall | Session fixation, CSRF (use SameSite) | Token theft via XSS, bloated claims | Confusing authn vs authz scopes | Misconfigured redirect URIs |
Common Confusions Cleared Up
Authentication = who are you? Authorization = what can you do? Sessions/JWT/OIDC handle authentication context. OAuth scopes handle authorization (what resources the client may access).
OAuth is a flow/protocol (authorization code, client credentials, etc.). JWT is one possible token format OAuth can issue. You can have OAuth without JWT (opaque tokens) and JWT without OAuth (custom login issuing JWT).
JWT wins on horizontal scale (no session lookup). Session wins on instant revocation and smaller client payload. Hybrid: short-lived JWT + refresh token stored server-side or in HttpOnly cookie.
Variations / Implementations
1) Session cookie (classic web)
Login → server creates session in Redis/DB → sets Set-Cookie: sessionId=...; HttpOnly; Secure; SameSite=Lax. Every request validates session ID against store.
2) JWT bearer token (API / microservices)
Login → server signs JWT with claims (sub, roles, exp) → client sends Authorization: Bearer <token>. Services verify signature with shared/public key — no session lookup.
3) OAuth 2.0 Authorization Code + PKCE (SPA/mobile)
Client redirects to IdP → user consents → auth code returned → client exchanges code for access + refresh tokens. PKCE prevents code interception on public clients.
4) OIDC social login
OAuth flow + ID token (JWT) containing sub, email, name. Your app creates/links local user record from IdP identity — you never see the user's Google password.
Common Mistakes / Anti-Patterns
- Storing JWT in localStorage/sessionStorage (XSS exfiltration).
- Long-lived access tokens with no revocation path.
- Putting roles/permissions in JWT that change often — stale until expiry.
- Using OAuth implicit flow for new apps (deprecated; use auth code + PKCE).
- Trusting JWT claims without signature verification.
- Confusing "OAuth login" with building your own password auth.
Interview Q&A
Likely Follow-up Questions
- How would you implement logout across all devices?
- How do microservices validate tokens without calling auth server every request?
- What is the BFF (Backend-for-Frontend) pattern and why does it help SPAs?