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.
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.
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
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
:
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:
Angular HttpClient
handles protection against Cross-Site Request Forgery attacks by default, as we can read in the documentation:
https://angular.io/guide/http#security-xsrf-protection
HttpClient
supports a common mechanism used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by defaultXSRF-TOKEN
, and sets it as an HTTP header,X-XSRF-TOKEN
.
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.
https://github.com/angular/angular/blob/ff9f4de4f1147ba9ea6d17c31442a2eedcf4e0d2/packages/common/http/src/xsrf.ts#L77
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.
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);
}
}
}
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:
/api
part from URLS); localhost
.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.
Spring Security allows us to use role-based control to restrict access to API resources. However,…
A custom annotation in Spring Boot tests is an easy and flexible way to provide…
Delegating user management to Keycloak allows us to better focus on meeting the business needs…
Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…
Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can…
Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…