DEV Community

Aswani Nayak
Aswani Nayak

Posted on

Enabling CSRF in a JWT-Based React + Spring Boot Application (Enterprise-Ready Approach)

Modern applications often use:
• React SPA (Single Page Application)
• Spring Boot backend
• JWT-based authentication
• Stateless REST APIs

In this architecture, it’s common to disable CSRF:
Code
.csrf(AbstractHttpConfigurer::disable)
However, many enterprise security teams and tools (e.g., GitHub CodeQL) flag this as a high-severity issue.
This blog explains:
• ✅ Why CSRF is flagged
• ✅ Whether JWT APIs need CSRF
• ✅ How to enable CSRF without breaking your SPA
• ✅ A production-ready implementation

1️⃣ Understanding the Problem
What is CSRF?
CSRF (Cross-Site Request Forgery) is an attack where a malicious website tricks a browser into making an authenticated request to another site.
CSRF attacks rely on:
• Automatic cookie sending by the browser
• Session-based authentication
Spring Security enables CSRF by default for this reason.

Why JWT APIs Typically Disable CSRF
In a JWT-based system:
• Authentication is done via:
• Authorization: Bearer
• Browsers do NOT automatically attach Authorization headers.
• No session cookies are involved.
• The API is stateless.
Therefore, CSRF protection is not strictly required.
However...

Why Security Teams Still Want It Enabled
Enterprise AppSec teams often require:
• Defense-in-depth
• Uniform policy enforcement
• Future-proof protection (in case cookies are introduced later)
• Clean security scans without exceptions
Instead of disabling CSRF, we can implement it correctly for SPA.

2️⃣ Enterprise-Ready Solution
We will:
• ✅ Keep JWT authentication
• ✅ Keep backend stateless
• ✅ Enable CSRF
• ✅ Make it work with React SPA
• ✅ Pass security review
The key is:
Code
CookieCsrfTokenRepository

3️⃣ Spring Boot Implementation
Step 1: Security Configuration
Code

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
            // ✅ Enable CSRF with Cookie repository (SPA-friendly)
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            )

            // ✅ Keep application stateless
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )

            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/**").permitAll()
                .anyRequest().authenticated()
            )

            // ✅ Add JWT filter
            .addFilterBefore(jwtAuthenticationFilter,
                    UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}
Enter fullscreen mode Exit fullscreen mode

What This Does
Spring Security will:

  1. Generate a CSRF token
  2. Send it in a cookie named: XSRF-TOKEN
  3. Expect the frontend to send it back in header: X-XSRF-TOKEN This pattern is SPA-friendly and secure.

4️⃣ React Implementation
Step 1: Enable Credentials
When using cookies, you must enable credentials:
Code

const api = axios.create({
  baseURL: "http://localhost:8080",
  withCredentials: true
});
Enter fullscreen mode Exit fullscreen mode

Step 2: Read CSRF Cookie
Helper function:
Code

function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Axios Interceptor
Code

api.interceptors.request.use((config) => {

  const jwt = localStorage.getItem("jwt");
  const csrfToken = getCookie("XSRF-TOKEN");

  if (jwt) {
    config.headers.Authorization = `Bearer ${jwt}`;
  }

  if (csrfToken) {
    config.headers["X-XSRF-TOKEN"] = csrfToken;
  }

  return config;
});
Enter fullscreen mode Exit fullscreen mode

Now every request sends:
• ✅ JWT token
• ✅ CSRF token

5️⃣ CORS Configuration (Critical)
When using cookies, you must allow credentials.
Code

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(List.of("http://localhost:3000"));
    configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
    configuration.setAllowedHeaders(
        List.of("Authorization", "Content-Type", "X-XSRF-TOKEN")
    );
    configuration.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source =
            new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}
Enter fullscreen mode Exit fullscreen mode

And enable CORS:
Code
http.cors(Customizer.withDefaults());

6️⃣ Request Flow Explained
Initial Request
• Backend generates CSRF token
• Sends XSRF-TOKEN cookie
API Call
React sends:
Authorization: Bearer
X-XSRF-TOKEN:
Spring Validates
• JWT signature
• CSRF token match
If both valid → request succeeds.

7️⃣ Architecture Summary

8️⃣ Why This Is a Strong Design
Even though JWT Authorization header authentication does not require CSRF, enabling it provides:
• Defense-in-depth
• Protection against future cookie-based changes
• Compliance with security standards
• Cleaner audit results
This approach balances:
✅ Security
✅ Architecture purity
✅ Enterprise compliance

9️⃣ Final Thoughts
If you are building:
• React SPA
• Spring Boot backend
• JWT authentication
And your security team requires CSRF to be enabled:
✅ Use CookieCsrfTokenRepository
✅ Send CSRF token via header
✅ Keep the application stateless
This gives you:
• Modern architecture
• Strong security posture
• Enterprise-ready implementation

Top comments (1)

Collapse
 
pranav_gore_297555a5b7dc2 profile image
Pranav Gore

Hi, I hope you are doing well. We are a software development team. We hunt for US jobs using Us job profile. So we are looking for a senior developer who can work with us.
Your role is to take part in the job interviews and pass the interviews. If your English is fluent, we can work together. If you are interested, please kindly send me message. I will explain more detail. Thank you!
Whatsapp: +1 (351) 234-6532
Telegram: @lionking06230810