Skip to content

Commit 404b071

Browse files
committed
refactor(auth): implements HttpOnly Cookies to improve security
1 parent 1dfb98d commit 404b071

File tree

3 files changed

+61
-12
lines changed

3 files changed

+61
-12
lines changed

src/main/java/com/github/renancvitor/inventory/application/authentication/controller/AuthenticationController.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
import com.github.renancvitor.inventory.application.authentication.dto.JWTTokenData;
1111
import com.github.renancvitor.inventory.application.authentication.dto.LoginData;
1212
import com.github.renancvitor.inventory.application.authentication.service.AuthenticationService;
13+
import com.github.renancvitor.inventory.application.user.dto.UserSummaryData;
1314

15+
import jakarta.servlet.http.Cookie;
16+
import jakarta.servlet.http.HttpServletResponse;
1417
import jakarta.validation.Valid;
1518
import lombok.RequiredArgsConstructor;
1619

@@ -23,9 +26,21 @@ public class AuthenticationController {
2326
private final AuthenticationService authenticationService;
2427

2528
@PostMapping
26-
public ResponseEntity<JWTTokenData> authentication(@RequestBody @Valid LoginData data) {
29+
public ResponseEntity<UserSummaryData> authentication(@RequestBody @Valid LoginData data,
30+
HttpServletResponse response) {
2731
JWTTokenData jwtTokenData = authenticationService.authentication(data, authenticationManager);
28-
return ResponseEntity.ok(jwtTokenData);
32+
33+
Cookie cookie = new Cookie("access_token", jwtTokenData.token());
34+
cookie.setHttpOnly(true);
35+
cookie.setSecure(true);
36+
cookie.setPath("/");
37+
cookie.setMaxAge(60 * 60 * 2);
38+
39+
cookie.setAttribute("SameSite", "Lax");
40+
41+
response.addCookie(cookie);
42+
43+
return ResponseEntity.ok(jwtTokenData.user());
2944
}
3045

3146
}

src/main/java/com/github/renancvitor/inventory/infra/security/SecurityFilter.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import jakarta.servlet.FilterChain;
1515
import jakarta.servlet.ServletException;
16+
import jakarta.servlet.http.Cookie;
1617
import jakarta.servlet.http.HttpServletRequest;
1718
import jakarta.servlet.http.HttpServletResponse;
1819

@@ -59,10 +60,16 @@ protected void doFilterInternal(
5960
}
6061

6162
private String recoveryToken(HttpServletRequest request) {
62-
String authorizationHeader = request.getHeader("Authorization");
63-
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
64-
return authorizationHeader.substring(7);
63+
if (request.getCookies() == null) {
64+
return null;
6565
}
66+
67+
for (Cookie cookie : request.getCookies()) {
68+
if ("access_token".equals(cookie.getName())) {
69+
return cookie.getValue();
70+
}
71+
}
72+
6673
return null;
6774
}
6875
}

src/test/java/com/github/renancvitor/inventory/controller/authentication/AuthenticationControllerTests.java

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import static org.junit.jupiter.api.Assertions.assertNotNull;
55
import static org.junit.jupiter.api.Assertions.assertThrows;
66
import static org.mockito.ArgumentMatchers.any;
7+
import static org.mockito.Mockito.mock;
8+
import static org.mockito.Mockito.verify;
9+
import static org.mockito.Mockito.verifyNoInteractions;
710
import static org.mockito.Mockito.when;
811

912
import org.junit.jupiter.api.BeforeEach;
@@ -21,6 +24,10 @@
2124
import com.github.renancvitor.inventory.application.authentication.dto.JWTTokenData;
2225
import com.github.renancvitor.inventory.application.authentication.dto.LoginData;
2326
import com.github.renancvitor.inventory.application.authentication.service.AuthenticationService;
27+
import com.github.renancvitor.inventory.application.user.dto.UserSummaryData;
28+
29+
import jakarta.servlet.http.Cookie;
30+
import jakarta.servlet.http.HttpServletResponse;
2431

2532
@ExtendWith(MockitoExtension.class)
2633
@ActiveProfiles("test")
@@ -48,25 +55,45 @@ void setup() {
4855
class PositiveCases {
4956
@Test
5057
void shouldReturn200AndJWTTokenDataWhenAuthenticationSucceeds() {
51-
when(authenticationService.authentication(any(LoginData.class), any(AuthenticationManager.class)))
58+
HttpServletResponse response = mock(HttpServletResponse.class);
59+
60+
when(authenticationService.authentication(
61+
any(LoginData.class),
62+
any(AuthenticationManager.class)))
5263
.thenReturn(jwtTokenData);
5364

54-
ResponseEntity<JWTTokenData> response = authenticationController.authentication(loginData);
65+
ResponseEntity<UserSummaryData> result = authenticationController.authentication(loginData, response);
66+
67+
assertNotNull(result);
68+
assertEquals(200, result.getStatusCode().value());
69+
assertEquals(jwtTokenData.user(), result.getBody());
70+
71+
verify(authenticationService).authentication(
72+
any(LoginData.class),
73+
any(AuthenticationManager.class));
5574

56-
assertNotNull(response);
57-
assertEquals(200, response.getStatusCode().value());
58-
assertEquals(jwtTokenData, response.getBody());
75+
verify(response).addCookie(any(Cookie.class));
5976
}
6077
}
6178

6279
@Nested
6380
class NegativeCases {
6481
@Test
6582
void shouldPropagateExceptionWhenServiceThrows() {
66-
when(authenticationService.authentication(any(LoginData.class), any(AuthenticationManager.class)))
83+
HttpServletResponse response = mock(HttpServletResponse.class);
84+
85+
when(authenticationService.authentication(
86+
any(LoginData.class),
87+
any(AuthenticationManager.class)))
6788
.thenThrow(new RuntimeException("Auth failed"));
6889

69-
assertThrows(RuntimeException.class, () -> authenticationController.authentication(loginData));
90+
assertThrows(RuntimeException.class, () -> authenticationController.authentication(loginData, response));
91+
92+
verify(authenticationService).authentication(
93+
any(LoginData.class),
94+
any(AuthenticationManager.class));
95+
96+
verifyNoInteractions(response);
7097
}
7198
}
7299
}

0 commit comments

Comments
 (0)