4

I am working on a Angular11 frontend app coupled with a Java Spring boot backend.

Context

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))
        );
    }

My issue

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) enter image description here 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.

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

I updated my question with some more details. I tried your suggestion and it is a good clue, but it still does not work in my case. My cookie is still not sent with the /refreshtoken request.
Can you add the .ts part for authentication as well?
You actually put me on the right way. My mistake was to put withCredentials: true only for the /refreshtoken request. But actually, I have to put it on /authenticate request as well which allows it to set the cookies in the browser ! thank you !
2

As @TCH mentioned, for the API which returns the Set-Cookie header we need to set request header with withCredentials: true. I also did a similar mistake and I want to highlight this point :-)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.