Session-Based vs Token-Based Authentication
Fast recall
Quick revision
Core answer
Whether the candidate understands stateful vs stateless architecture trade-offs, not just syntax.
Claiming JWTs are always better — missing that revocation, token size, and secret management are real drawbacks.
Discuss opaque tokens with an introspection endpoint as a middle ground, or refresh token rotation as a revocation strategy.
Interview-ready guide
Interview answer
Two models, opposite trade-offs
- Session-based: server-side state, instant revocation, needs shared store in clusters
- JWT-based: stateless, scales horizontally, revocation hard until expiry
Session-based flow
- Login -> server creates session, stores in memory or Redis -> client gets session cookie
- Each request: server looks up session ID -> authenticated
- Logout:
session.invalidate()— instant - Sticky sessions OR shared Redis required across instances
JWT-based flow
- Login -> server signs JWT with claims -> client stores it, sends in
Authorization: Bearer - Each request: server verifies signature — no DB lookup
- Revocation: build a Redis blocklist of revoked JTIs (now you have state)
- Refresh tokens with rotation give automatic theft detection
JWT payload is not secret
- Base64-decoded reveals everything
- Signed != encrypted
- Don't put passwords, PII, or secrets in claims
- Use a separate
/meendpoint for sensitive data
Opaque tokens — middle ground
- Random string, no embedded claims
- Stored in the auth server (Redis); each request introspects
- Revocable instantly, one Redis lookup per request
- Cache introspection 30-60s for performance
The architectural rule
- Web monolith with DB -> sessions, simpler
- API or microservice -> JWT, stateless
- OAuth2 server with revocation -> opaque + introspect
- Don't pick JWT just because it's trendy
Session-Based vs Token-Based Authentication
Direct Answer
Session-based authentication stores user state server-side, tied to a cookie — instant revocation, but needs sticky sessions or a shared store (Redis) in clusters. Token-based (typically JWT) is stateless: all data is encoded in the token, scaling horizontally with no shared state, but revocation is hard until the token expires. Sessions are simpler and correct for monolithic web apps; JWTs are right for APIs and microservices where each service independently validates tokens. Opaque tokens with introspection are the middle ground when you need stateless transport plus instant revocation.
Code Example
Session-based auth stores the user's identity server-side in an HTTP session; the cookie (JSESSIONID) is just a pointer. Token-based auth (typically JWT) is stateless — the token itself carries the identity and is validated on every request.
// -------- SESSION-BASED (traditional web apps) --------
@Bean SecurityFilterChain sessionChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.sessionManagement(s -> s
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1).maxSessionsPreventsLogin(true)) // single sign-on
.rememberMe(r -> r.key("secret").tokenValiditySeconds(86400))
.csrf(Customizer.withDefaults()) // required for sessions
.build();
}
// -------- TOKEN-BASED (SPAs, mobile, microservices) --------
@Bean SecurityFilterChain jwtChain(HttpSecurity http) throws Exception {
return http
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(c -> c.disable()) // no sessions = no CSRF
.authorizeHttpRequests(a -> a
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())) // validates Bearer JWTs
.build();
}
// Issuing a JWT on login
@PostMapping("/auth/login")
public Map<String, String> login(@RequestBody LoginRequest req) {
Authentication auth = authManager.authenticate(
new UsernamePasswordAuthenticationToken(req.username(), req.password()));
Instant now = Instant.now();
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self").issuedAt(now).expiresAt(now.plus(15, ChronoUnit.MINUTES))
.subject(auth.getName())
.claim("scope", auth.getAuthorities().stream().map(Object::toString).toList())
.build();
String token = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
return Map.of("access_token", token, "token_type", "Bearer");
}
| Aspect | Session | JWT |
|---|---|---|
| Server state | HttpSession per user | none |
| Scaling | sticky session or Redis store | trivially horizontal |
| Revocation | delete session row | denylist token until expiry |
| Size on wire | ~32-byte cookie | ~500-byte header per request |
| CSRF risk | yes (cookies auto-sent) | low if stored in memory, high if in cookies |
| Best fit | server-rendered apps | SPA, mobile, service-to-service |
- Sessions shine when you can keep server-side state and want instant revocation — e-commerce checkout, admin portals.
- JWTs shine when requests cross service boundaries and you want zero lookup cost — APIs, mobile, microservices.
- Do not store JWTs in
localStorageif you can avoid it — a single XSS flaw leaks the token. PreferHttpOnlycookies withSameSite=Strictor in-memory storage with silent refresh. - Either way, keep access-token lifetimes short (15 min) and rotate with refresh tokens.
Common Mistakes & Pitfalls
Claiming JWTs are always better — missing that revocation, token size, and secret management are real drawbacks.
Check your understanding
Answer these without looking back at the article.
- How would you implement session clustering in a Spring Boot application using Spring Session and Redis?
- What is refresh token rotation and how does it help mitigate JWT theft?
- How does Spring Security's OAuth2 Resource Server handle opaque token introspection?
- When would you mix sessions and JWTs in the same architecture (BFF pattern)?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.