spring-bootcsrf

Why does CSRF require client changes?


I have a Spring Boot application that serves a Restful API. The clients are a web application, a mobile app, and Postman.

I was asked to implement CSRF by our security team. I did so using a security filter chain and http.csrf(), as is well documented in many places, making sure to ignore endpoints such as login that don't require authentication.

My understanding is that clients need to capture a cookie called XSRF-TOKEN and return it in a header called X-XSRF-TOKEN with subsequent post, put or delete requests. The first thing I did was try to post something from Postman to confirm that CSRF was actually enabled and blocking requests (it was). Then I added pre-request and post-response scripts in Postman to capture and return the token, and that also works.

Next I turned to the mobile app, and implemented the same strategy, and that works too.

I am not a web developer, so I assigned that task to someone else, but then I started thinking about it. The whole purpose of cookies is so that the web browser automatically returns the cookie value to the server. This being the case, why does the web application need to explicitly copy the token into a header? Shouldn't it automatically just return that cookie to the server and shouldn't the server just be able to read it from the cookie? In fact, Postman also supports cookies so this should have worked there, too.

I assume that there is some technical or security reason why it doesn't work this way, but I really can't think of what it is.

It would definitely be more convenient, and more reliable, if the clients could depend on cookies doing the job instead of each client having to implement its own code.


Solution

  • The idea of manually copying the cookie value into a header is based on the fact that the cookie is limited to the domain it was set for (this is enforced by the browser)

    because of that ONLY the js code from this domain can read the cookie value. And therefore ONLY the js code from authorized domain can set the correct header value.

    So when you get a phishing email, and you unwantedly activate a hidden JS code to transfer 100 million EUR to a nigerian prince, the browser will by default include the cookie value BUT the nigerian royalty's JS code won't be able to read and therefore won't be able to include the CSRF value into header.

    This is how the server will know that this was an unauthorized API call and return 403 for example because of CSRF Filter in spring-security