Refactor AuthController to enhance refresh token handling and support CORS

This commit is contained in:
Vincent Guillet
2025-11-28 18:50:35 +01:00
parent 411c407a40
commit b8aa3e61ed

View File

@@ -1,13 +1,13 @@
package fr.gameovergne.api.controller.auth;
import fr.gameovergne.api.dto.auth.AuthRequest;
import fr.gameovergne.api.dto.auth.AuthResponse;
import fr.gameovergne.api.dto.user.UserDTO;
import fr.gameovergne.api.mapper.user.UserMapper;
import fr.gameovergne.api.service.auth.AuthService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import fr.gameovergne.api.dto.auth.AuthResponse;
import fr.gameovergne.api.dto.user.UserDTO;
import fr.gameovergne.api.dto.auth.AuthRequest;
import fr.gameovergne.api.mapper.user.UserMapper;
import fr.gameovergne.api.service.auth.AuthService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
@@ -17,6 +17,12 @@ import java.util.Arrays;
@RestController
@RequestMapping("/api/auth")
@CrossOrigin(
origins = "https://dev.vincent-guillet.fr",
allowCredentials = "true",
allowedHeaders = "*",
methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS}
)
public class AuthController {
private final AuthService authService;
@@ -25,26 +31,34 @@ public class AuthController {
this.authService = authService;
}
// ===================== REGISTER =====================
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(@RequestBody UserDTO dto) {
public ResponseEntity<AuthResponse> register(@RequestBody UserDTO dto,
HttpServletResponse response) {
return authService.register(UserMapper.fromDto(dto))
.map((ResponseEntity::ok))
.map(authResponse -> createAuthResponse(authResponse, response))
.orElse(ResponseEntity.badRequest().build());
}
// ===================== LOGIN =====================
@PostMapping("/login")
public ResponseEntity<AuthResponse> authenticate(@RequestBody AuthRequest request, HttpServletResponse response) {
public ResponseEntity<AuthResponse> authenticate(@RequestBody AuthRequest request,
HttpServletResponse response) {
return authService.authenticate(request)
.map(authResponse -> createAuthResponse(authResponse, response))
.orElse(ResponseEntity.badRequest().build());
}
// ===================== LOGOUT =====================
@GetMapping("/logout")
public ResponseEntity<Void> logout(HttpServletResponse response) {
// Supprime le cookie de refresh token
ResponseCookie cookie = ResponseCookie.from("refreshToken", "")
.httpOnly(true)
.secure(false) // true en prod
.secure(false) // true en prod derrière HTTPS
.path("/")
.maxAge(0) // expire immédiatement
.sameSite("Lax")
@@ -53,11 +67,16 @@ public class AuthController {
return ResponseEntity.ok().build();
}
// ===================== ME =====================
@GetMapping("/me")
public ResponseEntity<UserDTO> getCurrentUser(HttpServletRequest request) {
String username = request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : null;
String username = request.getUserPrincipal() != null
? request.getUserPrincipal().getName()
: null;
if (username == null) {
return ResponseEntity.status(401).build(); // Unauthorized
return ResponseEntity.status(401).build();
}
return authService.getCurrentUser(username)
@@ -65,20 +84,26 @@ public class AuthController {
.orElse(ResponseEntity.notFound().build());
}
// --------- REFRESH TOKEN : accepte POST et GET ---------
// ===================== REFRESH =====================
// Accepte POST et GET pour être robuste aux proxies / redirects bizarres
@PostMapping("/refresh")
public ResponseEntity<AuthResponse> refreshPost(HttpServletRequest request, HttpServletResponse response) {
public ResponseEntity<AuthResponse> refreshPost(HttpServletRequest request,
HttpServletResponse response) {
return handleRefresh(request, response);
}
@GetMapping("/refresh")
public ResponseEntity<AuthResponse> refreshGet(HttpServletRequest request, HttpServletResponse response) {
public ResponseEntity<AuthResponse> refreshGet(HttpServletRequest request,
HttpServletResponse response) {
return handleRefresh(request, response);
}
private ResponseEntity<AuthResponse> handleRefresh(HttpServletRequest request, HttpServletResponse response) {
private ResponseEntity<AuthResponse> handleRefresh(HttpServletRequest request,
HttpServletResponse response) {
String refreshToken = null;
if (request.getCookies() != null) {
refreshToken = Arrays.stream(request.getCookies())
.filter(c -> "refreshToken".equals(c.getName()))
@@ -94,13 +119,15 @@ public class AuthController {
return authService.refresh(refreshToken)
.map(authResponse -> createAuthResponse(authResponse, response))
// Token inconnu/expiré -> 204 aussi, pas d'erreur réseau
// token expiré / invalide -> 204 aussi (pas derreur réseau)
.orElse(ResponseEntity.noContent().build());
}
// --------- Utilitaire pour poser le cookie + réponse ---------
// ===================== UTILITAIRE =====================
private ResponseEntity<AuthResponse> createAuthResponse(AuthResponse authResponse, HttpServletResponse response) {
private ResponseEntity<AuthResponse> createAuthResponse(AuthResponse authResponse,
HttpServletResponse response) {
// Cookie HTTP-only pour le refresh
ResponseCookie cookie = ResponseCookie.from("refreshToken", authResponse.refreshToken())
.httpOnly(true)
.secure(false) // true en prod
@@ -110,7 +137,7 @@ public class AuthController {
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
// Ne pas renvoyer le refresh token dans le body
// On ne renvoie pas le refresh token dans le body
return ResponseEntity.ok(new AuthResponse(
authResponse.username(),
authResponse.accessToken(),