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

Top comments (0)