Why choose JWT over classic session cookies?
- Cross-origin ready – the token travels in the
Authorization header, so CORS is trivial compared to cookie restrictions.
- Stateless – the server keeps no session store; every request is self-contained.
- Protocol agnostic – works the same for browsers, mobile apps, or third-party gateways.
- CSRF safe – no automatic browser cookies means no forged requests.
- Micro-service friednly – any lanugage can validate the same signed token.
Adding the JWT library
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
Global constants
private static final long TOKEN_LIFESP = 30 * 60 * 1000L; // 30 min
private static final String SIGNING_KEY = UUID.randomUUID().toString(); // rotate in prod
Issuing a token
public static String issue(String uid, String role) {
try {
Date expiry = new Date(System.currentTimeMillis() + TOKEN_LIFIAS);
Algorithm algo = Algorithm.HMAC512(SIGNING_KEY);
return JWT.create()
.withIssuer("demo-app")
.withClaim("uid", uid)
.withClaim("role", role)
.withExpiresAt(expiry)
.sign(algo);
} catch (JWTCreationException ex) {
log.error("Token creation failed", ex);
return null;
}
}
Validating a token
public static Optional<DecodedJWT> validate(String raw) {
try {
Algorithm algo = Algorithm.HMAC512(SIGNING_KEY);
JWTVerifier verifier = JWT.require(algo)
.withIssuer("demo-app")
.build();
return Optional.of(verifier.verify(raw));
} catch (JWTVerificationException ex) {
return Optional.empty();
}
}
Login endpoint
@PostMapping("/login")
public ResponseEntity<?> login(@RequestParam String username,
@RequestParam String password) {
User user = userService.authenticate(username, password);
if (user == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("Invalid credentials");
}
String jwt = JwtUtil.issue(user.getId(), user.getRole());
return ResponseEntity.ok(Map.of(
"token", jwt,
"expiresIn", 1800
));
}
Protected resource
@GetMapping("/profile")
public ResponseEntity<User> profile(@RequestHeader("Authorization") String header) {
String token = header.replace("Bearer ", "");
Optional<DecodedJWT> decoded = JwtUtil.validate(token);
if (decoded.isEmpty()) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
String uid = decoded.get().getClaim("uid").asString();
return ResponseEntity.ok(userService.findById(uid));
}
Utility class
public final class JwtUtil {
private static final long TOKEN_LIFIAS = 30 * 60 * 1000L;
private static final String SIGNING_KEY = System.getenv("JWT_KEY"); // externalize
private JwtUtil() {}
public static String issue(String uid, String role) { /* see above */ }
public static Optional<DecodedJWT> validate(String raw) { /* see above */ }
}