Angular

Fix “Invalid CSRF token” error – add the XSRF-TOKEN header in Angular

Angular provides a built-in support for sending requests secured with the XSRF-TOKEN header. However, it won’t add the token to absolute URLs for security reasons. One way to solve the “Invalid CSRF token found” issue is to use relative links in all mutable requests and apply a custom proxy.

Debugging missing CSRF token

First, we have to be sure that Angular really doesn’t send requests with the X-XSRF-TOKEN. In the following section I’ll show you how to debug a Spring Boot application.

Adjust debug level on security logs

In order to see more detailed logs and enable security debugging add the following line to your application.properties file:

# src/main/resources/application-development.properties
…
logging.level.org.springframework.>

As a result, after restarting the app, you'll be able to read messages logged by the CsrfFilter class:

o.s.security.web.FilterChainProxy        : /api/login at position 5 of 14 in additional filter chain; firing Filter: 'CsrfFilter'
o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost:8080/api/login

Add a breakpoint

Now you know exactly where to set a breakpoint. In other words, add it in the CsrfFilter::doFilterInternal method (package: org.springframework.security.web.csrf) and verify that the actualToken is indeed null:

Fix the invalid CSRF token error

When inspecting the request in the developer console you'll see the 403 error on mutable requests because the Headers section is missing the X-XSRF-TOKEN:

Avoid absolute urls

Angular HttpClient handles protection against Cross-Site Request Forgery attacks by default, as we can read in the documentation:

HttpClient supports a common mechanism used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default XSRF-TOKEN, and sets it as an HTTP header, X-XSRF-TOKEN.

https://angular.io/guide/http#security-xsrf-protection

Nonetheless, we have to remember to avoid absolute paths when sending any mutable requests. The HttpXsrfInterceptor::intercept method is documented as this:

Skip both non-mutating requests and absolute URLs.
Non-mutating requests don't require a token, and absolute URLs require special handling anyway as the cookie set on our origin is not the same as the token expected by another origin.

https://github.com/angular/angular/blob/ff9f4de4f1147ba9ea6d17c31442a2eedcf4e0d2/packages/common/http/src/xsrf.ts#L77

With that in mind, we can decide to use relative paths in our requests. Below you can find an example based on the TaskDataService class:

// frontend/src/main/angular/src/app/tasks/services/task-data.service.ts
import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient } from '@angular/common/http';

import { Task } from '../task';

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable({
  providedIn: 'root'
})
export class TaskDataService {

  constructor(private http: HttpClient) { }

  public save(task: Task) {
    if(!task.id) {
      return this.http.post<Task>('api/tasks', task, httpOptions);
    }
  }
}

Configure proxy to a backend server

Now we need to divert all calls that use the /api path to a server, that in my example project, exposes endpoints on http://localhost:8080/api/*. In order to achieve that, we need to use the Angular proxy support. Create the following file in the src directory of your Angular project:

// src/proxy.conf.json
{
  "/api": {
    "target": "http://localhost:8080",
    "secure": false,
    "logLevel": "debug"
  }
}

Next, I'm going to configure the $ ng serve command in my angular.json to use the proxy file:

// src/main/angular/angular.json
…
"architect": {
…
  "serve": {
    …
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
      …
      "proxyConfig": "src/proxy.conf.json"
    },
…
Every time you update your proxy configuration rerun the $ ng serve command to make your changes effective.

Thanks to enabling the debug log level in my proxy.conf.json file I can read in my logs that the target was set properly:

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
: Compiled successfully.
…
[HPM] POST /api/tasks -> http://localhost:8080
…

Another key point is to read the Angular proxy documentation thoroughly in order to learn about:

To sum up, run the $ ng serve command and verify that the Angular app attaches the XSRF-TOKEN cookie and X-XSRF-TOKEN header to mutable requests as shown on the image below:

Furthermore, you can find all the work that I described here in the scrum-ally and jwt-spring-boot-angular-scaffolding projects.

Learn more on debugging "Invalid CSRF token found" error

Photo by Mingyue H from Pexels

little_pinecone

Recent Posts

Simplify the management of user roles in Spring Boot

Spring Security allows us to use role-based control to restrict access to API resources. However,…

3 years ago

Create a custom annotation to configure Spring Boot tests

A custom annotation in Spring Boot tests is an easy and flexible way to provide…

3 years ago

Keycloak with Spring Boot #4 – Simple guide for roles and authorities

Delegating user management to Keycloak allows us to better focus on meeting the business needs…

3 years ago

Keycloak with Spring Boot #3 – How to authorize requests in Swagger UI

Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…

3 years ago

Keycloak with Spring Boot #2 – Spring Security instead of Keycloak in tests

Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can…

3 years ago

Keycloak with Spring Boot #1 – Configure Spring Security with Keycloak

Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…

3 years ago