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();
}
}
β
What This Does
Spring Security will:
- Generate a CSRF token
- Send it in a cookie named: XSRF-TOKEN
- 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
});
β
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();
}
β
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;
});
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;
}
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













