I am working on a Angular11 frontend app coupled with a Java Spring boot backend.
I am trying to create an authentication with cookies with JWT. To do so I am inspired by
My endpoint /authenticate returns a jwt access token and set a jwt refresh token in the cookies (httpOnly).
My endpoint /refreshtoken gets the refresh token from the cookies and validates it before generating a new jwt access token and a new jwt refresh token.
Here is the code of my AuthenticationController
@RestController
@AllArgsConstructor
@CrossOrigin(origins = "http://localhost:4200", allowCredentials = "true")
public class AuthenticationController {
private final AuthenticationManager authenticationManager;
private final JwtTokenUtil jwtTokenUtil;
private final UserDetailsServiceImpl userDetailsService;
private final static String REFRESH_TOKEN_COOKIE = "resfreshtoken";
@PostMapping(value = "/authenticate")
public ResponseEntity<JwtAuthenticationResponse> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest, HttpServletResponse response) throws Exception {
try {
this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
}
catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
}
catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
final UserDetails userDetails = this.userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = this.jwtTokenUtil.generateToken(userDetails.getUsername());
final String refreshToken = this.jwtTokenUtil.generateRefreshToken(new HashMap<>(), userDetails.getUsername());
Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE, refreshToken);
cookie.setHttpOnly(true);
// cookie.setDomain("localhost");
// cookie.setPath("/");
response.addCookie(cookie);
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
}
@GetMapping(value = "/refreshtoken")
public ResponseEntity<?> refreshToken(@CookieValue(value = REFRESH_TOKEN_COOKIE, required = false) String refreshToken, HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies(); // ALWAYS returns null
log.debug("refreshToken {}", refreshToken); // ALWAYS null
try {
Claims claims = this.jwtTokenUtil.getAllClaimsFromToken(refreshToken);
final String username = claims.get("sub").toString();
final String newAccessToken = this.jwtTokenUtil.generateToken(username);
final String newRefreshToken = this.jwtTokenUtil.generateRefreshToken(claims, username);
CookieUtil.writeCookie(response, REFRESH_TOKEN_COOKIE, newRefreshToken);
return ResponseEntity.ok(new JwtAuthenticationResponse(newAccessToken));
}
catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) {
throw new BadCredentialsException("INVALID_CREDENTIALS", ex);
}
catch (ExpiredJwtException e) {
// user should re-login
}
return new ResponseEntity<>("Something went wrong", HttpStatus.BAD_REQUEST);
}
}
On Angular frontend, here are the options I add to my /refreshtoken Http request (withCredentials=true)
refreshToken(): Observable<AuthenticationResponse> {
return this.http.get<AuthenticationResponse>(this.apiUrl + 'refreshtoken', { withCredentials: true }).pipe(
tap(response => LocalStorageUtils.save(LocalStorageKey.JWT_ACCESS_TOKEN_KEY, response.jwtAccessToken))
);
}
If I try to reach my endpoint /authenticate then /refreshtoken from postman, everything works well and I can see the cookie
If I call /authenticate from my frontend, I can see the cookie in the response with the header SET-COOKIE (and in the cookies tab)
But then if I call /refreshtoken from my frontend, I cannot get the cookies with request.getCookies() nor @CookieValue() on the backend, it always returns null !
It seems like the cookie is well received on the frontend but not set anywere (I cannot see it in application tab -> cookies on Chrome).
Therefore it is not sent with the /refreshtoken request
If anyone can help or has an idea about what is going wrong, I would be grateful ! I can provide more details about my code if it is needed.
You need to add withCredentials: true to your httpOptions and pass it with HttpClient every request. You can also configure it globally with HttpClient and Interceptors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With