One item that has been nagging me, as we continue developing our framework and sample apps, is that the Java Web Token (JWT) is n the header of the request like this.
GET http://localhost:8080/someprotectedendpoint
Authorization: Bearer <jwt token>
While this works absolutely fine, there are some drawbacks in terms of security. While cookies mitigate some of the issues, they also have security issues of their own. My suggestion is, do your research and see how to mitigate those issues, then select the solution that works best for you, your group, your company.
The reason we are going with cookies are simplicity, portability and removing local storage.
With a react app, you have to decide where to store your JWT once it has been retrieved. The easiest thing to do is to save it into local storage and then retrieve it when you need to put it in request headers. Searching on google, you will quickly see this is not the recommended approach. Many smart people have identified other designs to manage jwts properly in a react application and my favorite is an oldie but goodie, cookies.
Why cookies? They’ve been around for a long time. They are simple. They are a portable solution. How much code do I need to add to my client application, in this case react, to support cookies? None. Everything needed to support storing and sending cookies is already there. On top of that, we get the benefit of having dozens, if not hundreds, of people making sure that cookies work. What else do we get? Portability. How much engineering effort did we have to put into porting cookie support to react? None. Is it highly likely that any other client application we will likely write in our lifetime will support cookies? Yes.
Now, let’s move on to how we migrate our backend SpringBoot server to support cookies.
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication, JwtTokenProvider.MAX_ACCESS_TOKEN_EXPIRATION);
Cookie cookie = new Cookie("token", jwt);
cookie.setHttpOnly(true);
cookie.setMaxAge(JwtTokenProvider.MAX_ACCESS_TOKEN_EXPIRATION);
cookie.setPath("/");
cookie.setSecure(true);
response.addCookie(cookie);
return ResponseEntity.ok().build();
In the above code sample you can see that we create a new cookie, called token, and send it back in the response.
To use that token when it’s sent to the server, we will need to modify the jwt authentication filter, JwtAuthenticationFilter class, we have to look for it.
// Check for the jwt token in a cookie.
Cookie cookie = null;
if (request.getCookies() != null) {
cookie = Arrays.stream(request.getCookies()).filter(c -> c != null && c.getName().equals("token")).findFirst().orElse(null);
}
if (cookie != null) {
bearerToken = cookie.getValue();
}
On the client side, we don’t have to do anything to store the cookie. We only need to modify our requests to include them. If you are hosting the client and server app on the same site using the same port, you won’t have to do anything for this to happen. For deployments where the server and client are on different ports, you will have to set the with-credentials option so that cookies are sent.
// withCredentials is only needed if you have your client and server on different sites/ports
axios.get("http://localhost:8080/someprotectedendpoint", {withCredentials: true}).then(response => {
if (response.status === 200) {
this.setState({data: response.data})
}
}).catch(() => {
});
If you found this helpful, and you happen to be looking for hosting, please consider Dreamhost. If you’d like to sign up with them and feel inclined to throw me some credit, please use my referral link https://www.dreamhost.com/r.cgi?571777.