
Think of token-based authentication like a festival pass. When we first buy our ticket (log in), instead of the festival keeping track of all active visitors in their system, they give us a special tamper-proof wristband (the token). This wristband contains all the information needed: our identity, what exhibits we can access, and when the pass expires.
// Traditional session-based approach (stateful)
@Service
public class SessionAuthService {
private final SessionStore sessionStore; // Server needs to maintain state
public boolean validateSession(String sessionId) {
return sessionStore.getSession(sessionId) != null; // Must check against stored state
}
}
// Token-based approach (stateless)
@Service
public class TokenAuthService {
private final String secretKey;
public boolean validateToken(String token) {
try {
// All necessary information is in the token itself
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
return !isTokenExpired(claims); // No database lookup needed
} catch (JwtException e) {
return false;
}
}
}
The beauty of statelessness is that each request is self-contained. The server doesn't need to remember anything about the client's previous requests. This is like the festival not needing to maintain a list of current visitors - they just verify each wristband as people enter different exhibits.
Because tokens are stateless, they excel in distributed systems.
@Configuration
public class LoadBalancerConfig {
// With sessions, you'd need this kind of configuration
@Bean
public SessionRegistry sessionRegistry() {
// Complex setup to synchronize sessions across servers
return new DistributedSessionRegistry(redisTemplate);
}
}
// With tokens, no special configuration needed!
@Service
public class DistributedAuthService {
// Any server can validate the token independently
public boolean validateRequest(String token) {
return jwtService.validateToken(token);
}
}
Think of it like having multiple entrances to the festival. With traditional sessions, each guard would need to radio check if a visitor is allowed in. With tokens, each guard can independently verify the wristband.
Tokens use cryptographic signatures to ensure they haven't been tampered with:
@Service
public class JwtSecurityService {
private final String secretKey;
public String createToken(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
// The signature is crucial for integrity
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public boolean isTokenValid(String token) {
try {
// If the signature is invalid, this will throw an exception
Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return true;
} catch (SignatureException e) {
// Token has been tampered with
return false;
} catch (ExpiredJwtException e) {
// Token has expired
return false;
}
}
}
This is like having a magic marker ink on the wristband - it's virtually impossible to forge, and any attempt to modify it would be immediately noticeable!!
@RestController
public class AuthController {
@PostMapping("/login")
public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) {
// 1. Validate credentials
Authentication auth = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
// 2. Generate token with user information
String token = jwtService.createToken((UserDetails) auth.getPrincipal());
// 3. Return token to client
return ResponseEntity.ok(new TokenResponse(token));
}
@GetMapping("/protected-resource")
public ResponseEntity<?> getResource(@RequestHeader("Authorization") String authHeader) {
// 4. Extract and validate token from each request
String token = authHeader.substring(7); // Remove "Bearer "
if (jwtService.validateToken(token)) {
// 5. Process the request
return ResponseEntity.ok(resourceService.getResource());
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // Tokens handle CSRF protection
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(
new JwtAuthenticationFilter(jwtService),
UsernamePasswordAuthenticationFilter.class
)
.build();
}
}
Takeaways :