Course Content
Spring Security Series
0/28
Spring Security

Mastering CSRF Protection in Spring Security: A Comprehensive Developer’s Guide

In the world of web application security, few vulnerabilities are as widespread and misunderstood as Cross-Site Request Forgery (CSRF). It’s an insidious attack that leverages the trust a web application has in its users, turning their own browsers against them to perform unauthorized actions. Fortunately, for developers using the Spring ecosystem, Spring Security provides a powerful, robust, and highly configurable defense mechanism against this threat.

This comprehensive guide will take you on a deep dive into CSRF protection within Spring Security. We’ll start with the fundamentals of the attack, explore how Spring’s default configuration protects you, and then walk through the practical steps of integrating this protection with both traditional server-side rendered applications and modern Single-Page Applications (SPAs). By the end, you’ll not only understand the “what” and “why” but also the “how” of implementing ironclad CSRF protection in your projects.

The Anatomy of a CSRF Attack

Before we can defend against an attack, we must first understand how it works. A CSRF attack tricks an authenticated user into submitting a malicious request to a web application they are currently logged into. The core of the attack relies on one simple fact: browsers automatically include authentication tokens, like session cookies, with every request sent to a given domain.

Let’s illustrate with a simple scenario:

  1. Authentication: You log into your online banking website, my-bank.com. The server creates a session for you and sends a session cookie back to your browser. Your browser will now automatically attach this cookie to every future request to my-bank.com.
  2. The Trap: While still logged in, you browse to another website, malicious-site.com, which has been set up by an attacker. This site contains a hidden form that is automatically submitted when you load the page.
  3. The Forgery: This hidden form is designed to perform an action on your banking website, such as transferring money. The form might look something like this:
    <form action="https://my-bank.com/api/transfer" method="POST">
        <input type="hidden" name="recipient" value="attacker-account" />
        <input type="hidden" name="amount" value="1000" />
    </form>
    <script>document.forms[0].submit();</script>
    
  4. The Execution: Your browser, seeing a POST request to my-bank.com, dutifully attaches your session cookie and sends it. The bank’s server receives the request. It sees a valid session cookie, authenticates you, and processes the transfer. It has no way of knowing that you, the user, did not initiate this action.

The scary part is that you, the user, might not see anything at all. The bank executed a transaction on your behalf without your consent, all because it couldn’t distinguish a legitimate request from a forged one.

Spring Security’s Solution: The Synchronizer Token Pattern

To combat this, Spring Security implements a widely recognized and effective defense: the Synchronizer Token Pattern. The logic is to require that every state-changing request (like POST, PUT, DELETE) includes a secret, unique token that the attacker’s website cannot guess or access.

Here’s how it works:

  • When a user first visits the site or logs in, the server generates a cryptographically secure, random token.
  • This token is stored on the server-side, typically in the user’s HttpSession.
  • The server also sends this token to the client. How it’s sent depends on the application type (more on this later).
  • For any subsequent request that changes state, the client must include this CSRF token.
  • When the server receives the request, it performs a crucial check: it compares the token submitted in the request with the token stored in the user’s session.
  • If the tokens match, the request is deemed legitimate and is processed. If they don’t match, or if the token is missing, the server rejects the request with an HTTP 403 Forbidden error.

This pattern effectively thwarts the attack because the attacker on malicious-site.com has no way to know the correct CSRF token value for your session on my-bank.com. The browser’s Same-Origin Policy prevents the malicious site’s JavaScript from reading cookies or response data from the banking site, so it cannot steal the token and include it in its forged request.

Enabling CSRF Protection: The Easy Way

One of the best features of Spring Security is its “secure by default” philosophy. Since Spring Security 4, CSRF protection is enabled by default. If you are using a standard Spring Boot setup with the security starter, you are already protected. A minimal security configuration looks like this:

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.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .anyRequest().authenticated()
            )
            .formLogin();
        return http.build();
    }
}

In the configuration above, you don’t even see a call to enable CSRF. That’s because the .csrf() method is called implicitly with its default settings. By default, it protects against all unsafe HTTP methods: POST, PUT, DELETE, and PATCH. It automatically ignores safe methods like GET, HEAD, and OPTIONS.

A Look Under the Hood: Key Components

To master CSRF protection, it’s helpful to understand the main components that work together behind the scenes.

The CsrfFilter

This is the workhorse. The CsrfFilter is added to Spring’s security filter chain and intercepts incoming requests. It checks if the request requires protection (i.e., it’s a state-changing method) and then coordinates with the other components to validate the token.

The CsrfTokenRepository

This component is responsible for saving and loading the CSRF token. Spring Security provides two primary implementations:

  • HttpSessionCsrfTokenRepository (Default): This repository stores the token in the HttpSession. It’s simple, secure, and perfect for traditional, stateful web applications that use server-side rendering.
  • CookieCsrfTokenRepository: This repository stores the token in a cookie. This is the ideal choice for stateless applications or those using JavaScript frameworks (like React, Angular, or Vue) that need to access the token on the client-side.

Front-End Integration: Making It All Work

Knowing the theory is great, but the most common point of failure is integrating the protection with the front-end. The approach differs significantly between server-side and client-side applications.

Case 1: Server-Side Rendering (Thymeleaf, JSP)

This is where Spring Security truly shines. When using a templating engine like Thymeleaf, the integration is practically automatic. Spring Security’s CsrfRequestDataValueProcessor automatically detects forms and injects the CSRF token as a hidden input field.

A simple login form in Thymeleaf might look like this:

<form th:action="@{/login}" method="post">
    <div><label> Username : <input type="text" name="username"/> </label></div>
    <div><label> Password: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="Sign In"/></div>
</form>

When this is rendered, the final HTML sent to the browser will contain the magic ingredient:

<form action="/login" method="post">
    <!-- ... other inputs ... -->
    <input type="hidden" name="_csrf" value="a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890" />
</form>

When the form is submitted, the _csrf parameter is sent along with it, and the CsrfFilter validates it. You don’t have to write any extra code.

Case 2: Single-Page Applications (SPAs) and REST APIs

This is where developers often get stuck. SPAs communicate with the backend via AJAX/Fetch requests, so we can’t rely on hidden form fields. This is the perfect use case for the CookieCsrfTokenRepository.

Here’s the complete, two-step process:

Step 1: Configure the Backend

First, we need to tell Spring Security to store the token in a cookie that JavaScript can read. We do this by configuring the CookieCsrfTokenRepository and setting its httpOnly flag to false.

import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

// ... inside your SecurityFilterChain bean ...
http.csrf(csrf ->
    csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);

Now, when a client makes its first request, Spring Security will set a cookie named XSRF-TOKEN containing the token value.

Step 2: Configure the Frontend (e.g., with Axios)

The frontend application must now be configured to do two things: read the token from the XSRF-TOKEN cookie and send it back in a header (by default, X-XSRF-TOKEN) with every subsequent state-changing request. Many HTTP clients can be configured to do this automatically. Here is a standard configuration for Axios:

import axios from 'axios';

// Axios will now automatically read the token from the cookie 
// and set the header on cross-site requests.
// For many setups, setting these defaults is enough.
axios.defaults.xsrfCookieName = 'XSRF-TOKEN';
axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN';

// Now, any POST, PUT, or DELETE request you make with Axios
// will automatically include the CSRF token header.
axios.post('/api/data', { item: 'New Item' });

With this setup, your SPA can now securely communicate with your Spring Boot backend, fully protected against CSRF attacks.

When (and How) to Disable CSRF Protection

While CSRF protection is critical, there are specific scenarios where it is not necessary and can be disabled. The primary use case is for applications that are truly stateless and do not use cookies for authentication. If your API is secured using a mechanism like JWT or OAuth2 Bearer Tokens, where the client must manually add an Authorization header to every request, CSRF is not a relevant threat.

To disable it, you use the .disable() method in your security configuration:

// ... inside your SecurityFilterChain bean ...
http.csrf(csrf -> csrf.disable());

Warning: Do not disable CSRF protection just because your AJAX calls are failing. This is almost always a sign of a misconfiguration on the frontend. Disabling this crucial security layer should only be done when you are certain your authentication strategy is immune to CSRF attacks.

Troubleshooting the Dreaded 403 Forbidden

The most common symptom of a CSRF misconfiguration is receiving an HTTP 403 Forbidden response from the server. If you encounter this, run through this checklist:

  1. Is the request method something other than GET, HEAD, or OPTIONS? If so, it requires a token.
  2. For Server-Side Apps: View the HTML source of your page. Does your <form> tag contain a hidden input field named _csrf with a value?
  3. For SPAs: Open your browser’s developer tools and inspect the failing network request.
    • Check the request headers. Is the X-XSRF-TOKEN header present?
    • Check the cookies for the request. Is the XSRF-TOKEN cookie being sent?
    • Do the values of the cookie and the header match?

Working through these steps will almost always reveal the source of the problem, allowing you to fix the integration rather than disabling the protection.

Conclusion

Cross-Site Request Forgery remains a potent threat to web applications, but the Spring Security framework provides developers with a first-class, comprehensive solution. By defaulting to a secure state and offering flexible configuration options, it empowers you to protect your applications effectively. Whether you are building a traditional server-rendered application with Thymeleaf or a modern SPA with React or Angular, understanding the Synchronizer Token Pattern and knowing how to configure the HttpSessionCsrfTokenRepository versus the CookieCsrfTokenRepository is the key to a successful and secure implementation. By mastering these concepts, you can ensure your application remains safe, and your users’ data stays protected.

Scroll to Top