CSRF configuration for Angular + Spring Boot application

In this article we will describe how to configure an Angular frontend / Spring Boot backend application to be secure against CSRF attacks.

What is CSRF?

Cross Site Request Forgery (CSRF or XSRF) is an attack against a web application in which a malicious site tricks the victim application users into sending unintended requests to that application. For a full explanation of this type of attack please check this article.

Our case

In this scenario we have an Angular frontend application that communicates over HTTP with various REST endpoints served by a Spring Boot backend application. In order the application to be secure against CSRF attacks it has to follow these steps:

  1. The frontend app performs a GET request. GET requests are always accepted by the Spring Boot backend app. Make sure not to modify state with GET.
  2. The backend app provides a CSRF token in the form of a cookie in the response.
  3. Subsequent requests that modify state (PATCH, POST, PUT, DELETE) from the frontend app contain the cookie as well as the token as a request header. If not, the request gets rejected by the backend.

This secures the application because a malicious site cannot create a cookie to be sent to your site. No browser allows it and if it did, this would be a major security flaw for that browser.

Angular and Spring Boot frameworks both have built-in functionalities to protect against CSRF.

Angular configuration

Angular needs only know the name of the cookie mentioned earlier and the name of the header used to send the token. Angular handles the rest of the client's responsibility automatically. These names default to XSRF-TOKEN and X-XSRF-TOKEN respectively, so if these defaults suit you, then Angular requires zero configuration for CSRF protection. Follow this article for more details and how to change the default names.

Spring Boot configuration

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Bean
  public CsrfTokenRepository csrfTokenRepository() {
    CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
    repository.setCookiePath("/");
    return repository;
  }

  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.csrf().csrfTokenRepository(csrfTokenRepository());
    // Rest of security configuration
  }
}

The configured CookieCsrfTokenRepository is responsible for:

  1. Generating new CSRF tokens.
  2. Adding the generated tokens as a cookie to the GET response mentioned earlier in this article.
  3. Reading the token from the cookie on subsequent non-GET requests.

Note that we created the repository using withHttpOnlyFalse(). This is mandatory because otherwise we do not allow the client JavaScript code to access the cookie which it needs to do in order to add the token to the HTTP headers. More information on the HttpOnly cookie attribute can be found here.

Also, note the use of setCookiePath("/"). Here we configure the common context path between the frontend and backend applications. This means for example that if the frontend app is accessible at my.domain and the REST endpoints at my.domain/api, then, the cookie path must be set to "/"so that the browser allows the use of the cookie for both paths. If the fronend app is accessible at my.domain/app and the REST endpoints at my.domain/app/api, then you should use "/app" as the cookie path to prevent other sites within the domain to access the cookie.

The default cookie and header names are the same as in Angular. If you need to change them, then you can do so as follows in the csrfTokenRepository method:

repository.setCookieName("NEW_COOKIE_NAME");
repository.setHeaderName("NEW_HEADER_NAME");

Results

GET request that sets the CSRF token as a cookie.

GET.png

PUT request that contains the cookie and the HTTP header.

PUT.png