From stateful to stateless RESTful security using Spring and JWTs – Part 5 (Stateless CSRF)

During the previous posts in this series, we managed to move from a stateful to a stateless authentication solution using JWTs.

But does our solution hold against cross-site request forgery (CSRF) attacks?
In two words: it depends. In more than two words: it depends on where JWT tokens are stored on the client site. If they are stored as cookies, then some CSRF defense is worth the extra effort.

So pack your bag and fire up your IDE! (or let Git do the work by pointing it to this GitHub tag. The choice is yours!).

Related posts

Know thy enemy

Imagine you are currently logged to mysupadupasite.com. You know the site is pretty much secure, so no worries there.
But then open a new tab and browse to mydevilevilsite.com. You have not actually logged out of the supadupa site, so the cookie where the session ID or JWT is stored is still there.

Given that mydevilevilsite.com is… evil indeed, it will leverage that lingering cookie and send to mysupadupasite.com a request such a:

How’s that possible? Well, mydevilevilsite cannot see what is written in the mysupadupasite cookie, because it’s not set for the mydevilevilsite domain. But any request sent to mysupadupasite will automatically include any cookie set for it! So when the evil site does the request, the login cookie will be sent along with it. In practice that means that, with some knowledge of the API being targeted, mydevilevilsite can perform actions on your behalf as long as the login cookie on your browser is valid and present.

Which brings us to the important question: as an adopter of stateless, JWT-based authentication, does this concern me?

Yes, but only if you store your JWT in a cookie.

In a forthcoming post we will see that storing JWTs in a cookie, even an http-only cookie, may not the best option. Then again, maybe you are in a situation where you absolutely need to store your tokens in cookies? If that is indeed the case, then keep on reading.

CSRF protection: the stateless way

In a stateful situation, the back end would generate and send the browser a token which has to be copied to an HTTP header before a request to the API endpoint is made. This way the server gets the token from the header, compares it with the one he sent to that browser for that session, and if both tokens match then it means the API request can be green-lighted.
This works because only the original website is able to read the cookie with that anti-CSRF token and copy it to the header. An evil website would not be able to read the cookie’s content, so it would not be able to copy it to the header. At best, it would be able to send that token cookie back to the server. That’s why it is important to have the cookie’s content (the token) copied to the header.

In a stateless situation the server will not remember which user got which CSRF token in order to verify it it was rightfully sent back at the next API request. That would mean that the server would have to maintain a user’s state, a session, which is exactly what we want to avoid.
However the back end still can check whether he got the same token as a cookie and as a HTTP header. That allows the server to make sure the API request is not a cross-site request forgery, since only the original website is able to write that token in a cookie with the right domain attribute. This is called the double submit cookie.

Implementing stateless CSRF

The client (the browser) will have to generate a decently-random (cryptographically strong) value that it will send with its request to the back end API. Thanks to the WebCrypto API, the generation of these values is quite easy to accomplish.
In the context of our JUnit test, we will simply use random UUIDs.

The improved version of the TestRestClient class we use testing our API endpoints now adds the generation of these tokens when logging in or when POSTing a request to the API:

As a reminder: this TestRestClient class’ scope is to simulate what a web-based application would do to access the RESTful endpoints.

The back end simply has to verify that the token values from the received HTTP header and cookie are equal. A filter will be used for that purpose:

Connecting this filter to the security chain is as simple as modifying one line of configuration in the SecurityConfiguration class:

And that’s it!

Feel free to run the tests in SecurityConfigurationTest to make sure that our filter is properly blocking any request that’s missing its CSRF tokens.

So, we’re done now?

My objective with this tutorial series was to provide a comprehensive overview on how to transition from stateful to stateless security: to leverage JWTs in conjunction with Spring Boot and Spring Security to obtain a basic, but functional stateless security solution. Strictly speaking, you can start implementing this in your project right now!

But should you?

No security solution is 100% perfect, and stateless security has its shortcomings too. Maybe the right choice for your project still is session-based authentication?
That’s why my next post will attempt discussing the pros and cons of stateless and stateful security solutions.

Until then,

Cheers!

Leave a Reply

Your email address will not be published. Required fields are marked *