How to Implement Refresh Token in Spring Boot Securely
To implement
refresh token in Spring Boot, create a separate token that allows users to get a new access token without re-authenticating. Store the refresh token securely (e.g., in a database) and validate it when clients request a new access token. Use JWT for access tokens and a persistent store for refresh tokens to maintain security.Syntax
The refresh token implementation in Spring Boot typically involves these parts:
- JWT Access Token: Short-lived token for API access.
- Refresh Token: Long-lived token stored securely to request new access tokens.
- Token Endpoint: API endpoint to exchange refresh token for a new access token.
- Token Storage: Database or cache to store refresh tokens safely.
Below is a simplified syntax pattern for issuing and validating refresh tokens.
java
public class TokenService { public String generateAccessToken(User user) { // Create JWT access token with short expiry return ""; // placeholder } public String generateRefreshToken(User user) { // Create refresh token (random string or JWT with longer expiry) // Save refresh token in DB linked to user return ""; // placeholder } public boolean validateRefreshToken(String token) { // Check token exists and is valid in DB return false; // placeholder } public String refreshAccessToken(String refreshToken) { if (validateRefreshToken(refreshToken)) { // Generate new access token return ""; // placeholder } else { throw new RuntimeException("Invalid refresh token"); } } }
Example
This example shows a simple Spring Boot REST controller with endpoints to login, get tokens, and refresh the access token using a refresh token.
java
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.*; @RestController @RequestMapping("/auth") public class AuthController { private final Map<String, String> refreshTokenStore = new HashMap<>(); // userId -> refreshToken @PostMapping("/login") public ResponseEntity<Map<String, String>> login(@RequestParam String userId) { // In real app, validate user credentials String accessToken = generateAccessToken(userId); String refreshToken = generateRefreshToken(userId); refreshTokenStore.put(userId, refreshToken); Map<String, String> tokens = new HashMap<>(); tokens.put("accessToken", accessToken); tokens.put("refreshToken", refreshToken); return ResponseEntity.ok(tokens); } @PostMapping("/refresh") public ResponseEntity<Map<String, String>> refresh(@RequestParam String userId, @RequestParam String refreshToken) { String storedToken = refreshTokenStore.get(userId); if (storedToken != null && storedToken.equals(refreshToken)) { String newAccessToken = generateAccessToken(userId); Map<String, String> token = new HashMap<>(); token.put("accessToken", newAccessToken); return ResponseEntity.ok(token); } else { return ResponseEntity.status(401).build(); } } private String generateAccessToken(String userId) { // Simulate JWT token generation with expiry return "access-token-for-" + userId + "-" + System.currentTimeMillis(); } private String generateRefreshToken(String userId) { // Simulate refresh token generation return UUID.randomUUID().toString(); } }
Output
POST /auth/login?userId=alice
Response: {"accessToken":"access-token-for-alice-1680000000000","refreshToken":"550e8400-e29b-41d4-a716-446655440000"}
POST /auth/refresh?userId=alice&refreshToken=550e8400-e29b-41d4-a716-446655440000
Response: {"accessToken":"access-token-for-alice-1680000005000"}
Common Pitfalls
- Not storing refresh tokens securely: Always store refresh tokens in a database or secure storage, never expose them in URLs or logs.
- Using long-lived access tokens: Access tokens should be short-lived to reduce risk if leaked.
- Not validating refresh tokens properly: Always check refresh token validity and expiration before issuing new access tokens.
- Not revoking refresh tokens on logout: Remove or invalidate refresh tokens when users log out to prevent misuse.
java
/* Wrong: No refresh token validation */ public String refreshAccessToken(String refreshToken) { // Directly issue new access token without checking refresh token return generateAccessTokenFromRefreshToken(refreshToken); } /* Right: Validate refresh token before issuing new access token */ public String refreshAccessToken(String refreshToken) { if (validateRefreshToken(refreshToken)) { return generateAccessTokenFromRefreshToken(refreshToken); } else { throw new RuntimeException("Invalid refresh token"); } }
Quick Reference
- Use JWT for short-lived access tokens.
- Generate secure, random refresh tokens and store them safely.
- Provide an API endpoint to exchange refresh tokens for new access tokens.
- Validate and revoke refresh tokens on logout or suspicious activity.
- Keep access tokens short-lived and refresh tokens longer but controlled.
Key Takeaways
Always store refresh tokens securely and validate them before issuing new access tokens.
Keep access tokens short-lived and use refresh tokens to maintain user sessions without re-login.
Provide a dedicated API endpoint to handle refresh token requests safely.
Revoke refresh tokens on logout or when suspicious activity is detected.
Use UUIDs or JWTs for refresh tokens and persist them in a database or cache.