I remember the day when I was talking to a colleague about the httpOnly cookie flag and how it could be used in combination with JWT token-based authentication. His idea was enough interesting for me to start working on it. My initial research revealed that some developers are also using this combination. So this article won’t bring something totally new on the table but rather provide an overview of my findings regarding the mentioned authentication.
When I was coding multipage applications with Rails or Laravel framework the whole authentication logic was already there. Because I was in the delivery mode I didn’t put a lot of attention on how the authentication works under the hood. However, I always wanted to learn more about it. When I switched to NodeJS and started writing REST APIs for SPA, I was finally able to dive deep into the authentication process.
Stateful VS Stateless
So the main difference between SPA vs Multipage application regarding the authentication is whether session data is stored on the server or not. Or in other words, multipage applications have a so-called stateful authentication approach and SPA have a stateless one.
The most common SPA’s data layer is provided by the REST API. And one of the architectural constraints of the RESTful API design is that every request must hold all data from the application state that is required to alter the resource state on the server. That constraint is the “ST” from REST - REpresentational State Transfer, which means that you are constantly sharing a state between server and client through the HTTP protocol.
JWT Token - the key component
Stateless REST API requires storing session data on the client-side. A key component for doing this is a JWT token which holds authentication data that can be confidently transmitted between clients.
So we need to find another solution to store JWT token on the browser.
Bring the good old cookies back
But with cookies, old security issues become relevant again. When you send a request, all cookies from the cookie domain are sent as well. This means that if a victim performs a request to the API from the attacker’s malicious site, also cookies, like session cookies, are sent. This security risk is known as CSRF - cross-site request forgery.
To mitigate CSRF risk we can use two things:
- SameSite cookie flag, practically, this means the cookie will only be sent if the site for the cookie matches the site currently shown in the browser's URL bar. As of writing, this flag is currently supported by 87 % of browsers according to “Can I Use” site.
Can you imagine what the main problem with this idea might be? A browser client can not read data that is sent via JWT token. This is especially important when the SPA needs to use session data for manipulating the app’s user interface. Like preventing some parts of the app to be shown to some user roles.
JWT token is constructed of three parts: payload, signature, and header.
If the payload has data that frontend needs, then we have to provide it somehow. A simple solution is that we split JWT token into two cookies, one that holds payload and one with signature and header data.
Payload cookie should have httpOnly flag set to false and signature.header cookie must have httpOnly flag set to true. Here is a diagram that shows the whole flow.
The main benefit of using this kind of authentication mechanism is, of course, the increased overall security of the app. But there is also one that is not so obvious at first glance and that’s a much better frontend developer experience. He simply doesn’t need to think anymore where to store the session data, all that he’s interested in is, how he’s going to read the content of the payload cookie.
The majority of work is done on the backend side, which is the right place to handle the authentication.