When an API is secured against CSRF attacks, we must ensure that our clients’ requests are adjusted to the security requirements. Learn how to successfully call an API that uses the Cookie-to-header token approach by adding the X-XSRF-TOKEN header to Postman requests.
Debugging the “Invalid CSRF token” error
We get the Invalid CSRF token
error when an API has csrf protection enabled and our request doesn’t contain the required data. The security configuration regarding the csrf protection in my example Spring Boot project looks like this:
1 2 3 4 |
public static void configureApiSecurity(HttpSecurity http) throws Exception { http .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) … |
The Spring documentation describes the CookieCsrfTokenRepository
as follows:
A CsrfTokenRepository that persists the CSRF token in a cookie named “XSRF-TOKEN” and reads from the header “X-XSRF-TOKEN”
https://docs.spring.io/spring-security/site/docs/5.0.13.RELEASE/api/index.html?org/springframework/security/web/csrf/CookieCsrfTokenRepository.html
As a result, the token will be present in the API responses as seen in the screenshot below:
Consequently, when I’m sending a POST request with no value provided in the required header, I get the 403 Forbidden in response. The exact error is specified in the application logs:
1 2 |
DEBUG 11528 --- [nio-8080-exec-3] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:8080/api/products DEBUG 11528 --- [nio-8080-exec-3] o.s.s.w.access.AccessDeniedHandlerImpl : Responding with 403 status code |
In a Spring Boot application, we can debug the actual value that is checked in the CsrfFilter class:
In my case, the actual value is null. So I need to add the X-XSRF-TOKEN header to my Postman POST requests.
Add XSRF token to Postman requests
I’m going to configure my Postman collection to get the current token from each request and save it as an environment variable. Then, I will use this variable as the header value for my POST calls.
Save the token value as an environment variable
To read the value of the cookie, we’re going to execute a short JavaScript code in Postman.
The sample collection I’m using for this article is in my public Postman workspace and is part of my keycloak-spring-boot
project. If you want to test my code locally (it requires Docker for running a Keycloak instance), visit the project repository on GitHub and follow the directions in the README.md file.
For all requests
First, I’m going to place the js code in the Collection testing space. As we can read in the documentation:
A test script associated with a collection will run after every request in the collection.
https://learning.postman.com/docs/writing-scripts/test-scripts/#testing-collections-and-folders
This way, I won’t have to add the script to every POST request I may create in this collection in the future.
I’m going to edit my example keycloak-spring-boot
collection:
Then, I’m going to add the following code to the Tests
tab:
1 2 |
var xsrfCookie = pm.cookies.get("XSRF-TOKEN"); pm.environment.set('xsrf-token', xsrfCookie); |
You can see the result in the screenshot below:
Finally, I’m going to save the changes to the collection by clicking the Update
button.
For a single request
Alternatively, I can only read the cookie after a selected request. To achieve this, I will need to add the js code in the request-specific test space and click the Save
button. In summary, the configuration of a single request will look like this:
In this case, we must remember to add the js code to any future POST request as well.
Add the header
First, I’m going to verify that the value is actually available as an environment variable in Postman after running my request. Therefore, I’m going to execute the request, click on the Environment quick look
button (the eye icon) and look for the xsrf-token
variable as shown in the screenshot below:
Now I’m going to add a new header to my request, with the following data:
- Key:
X-XSRF-TOKEN
, - Value:
{{xsrf-token}}
.
We can see the result in the screenshot below:
Verify the configuration
Finally, I can make my POST request with the certainty that it will work successfully:
Troubleshooting
What to check when API calls don’t work as planned?
403 Forbidden response status
- The JavaScript code we add will only run after a request. Therefore, we will get 403 on the first API call since the cookie value is not yet set to a variable. However, subsequent requests will have the appropriate value in the header.
- Is the
{{xsrf-token}}
variable set in Postman environment? You can see its current value in theEnvironment quick look
or by hovering over the variable. Select the proper environment (localhost
in my example) and make sure that the value is not empty. - Can you check API logs or debug the actual verified header value? If the value is null, the header is empty.
- Remember that even if we add the script to a collection-specific test space, we need to manually add the X-XSRF-TOKEN header to each POST request.
More on complying with the CSRF requirements
- How do I send spring csrf token from Postman rest client?
- Moreover, you can learn how to get a list of cookies associated with a request in the Scripting with request cookies docs.
- Read the Defining variables in scripts if want to set variables programmatically in your request scripts.
- Difference between CSRF and X-CSRF-Token.
- What is the difference between X-XSRF-TOKEN and X-CSRF-TOKEN?
- Spring Security Reference Guide on Cross Site Request Forgery.
- Fix “Invalid CSRF token” error – add the XSRF-TOKEN header in Angular.
- Postman Pre-Request script to append CSRF token in header for POST requests in Laravel Sanctum authenticated SPA.
Photo by Andrea Piacquadio from Pexels
what option to choose under ‘Authorization’?
I think it depends on the security configuration of the API you’re trying to call.
To test adding the token in Postman to call an API secured with Basic Auth:
clone the spring-boot-swagger-ui-basic-auth project and run the app locally;
select the
Basic Auth
Authorization Type and provide username and password (from the project’s README.md);follow the steps described in this article to add the token;
if the configuration works, the app returns
204 No Content
response on the DELETE endpoint.Below is a screenshot from my Postman:
Thanks, this article solved my problem.
Glad to have helped 🙂