CSRF Token Generation in Spring Boot
In Spring Boot (and Spring Security more generally), CSRF (Cross-Site Request
Forgery) tokens are generated and managed automatically by the framework
to protect against CSRF attacks.
When you include Spring Security in your Spring Boot project, CSRF protection
is enabled by default for web applications.
How CSRF Token is Generated
When a user accesses a secured web page:
Spring Security generates a unique CSRF token for the user session.
This token is associated with the user's HTTP session
(HttpSessionCsrfTokenRepository by default).
The token is typically generated using something like:
UUID.randomUUID().toString();
CSRF Token Repositories
Spring Security uses a CsrfTokenRepository to store and retrieve CSRF
tokens. Common implementations:
HttpSessionCsrfTokenRepository: stores token in the HTTP session
(default).
CookieCsrfTokenRepository: stores the CSRF token in a cookie (often
used for SPAs like Angular or React).
Example:
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
4. CSRF Token in the HTML Page
If you use Thymeleaf or JSP, Spring can automatically include the CSRF token in
forms:
Example (Thymeleaf):
<form method="post" th:action="@{/submit}">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="$
{_csrf.token}" />
...
</form>
For JavaScript-based clients (e.g., Angular, React), the token can be retrieved
from a cookie or an endpoint and sent via header.
CSRF Token Validation
When a POST, PUT, DELETE, or PATCH request is received:
Spring Security checks for the CSRF token in the request (header or form
field).
If the token is missing or invalid, a 403 Forbidden is returned.
Customizing or Disabling CSRF
You can disable CSRF protection (not recommended for browser-based clients):
http.csrf(csrf -> csrf.disable());
Or use it selectively for specific endpoints (e.g., enable it for web, disable it for
APIs).
Here's how it works:
Default CSRF Token Generation
1. Token Creation:
o Spring Security generates a random, cryptographically strong
token value
o By default, it uses SecureRandom to create a Base64-encoded string
o The token is typically 32-64 bytes long (exact length depends on
the implementation)
2. Token Storage:
o The token is stored in the HTTP session on the server side
o A copy is also sent to the client, typically in:
A hidden form field named _csrf (for form submissions)
Or as a header (for AJAX requests)
3. Token Validation:
o When a state-changing request (POST, PUT, DELETE, etc.) is made:
Spring Security compares the token from the request with
the one stored in the session
If they don't match, the request is rejected
Customization Options
You can customize CSRF token behavior in Spring Security:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// or use custom token repository
// .csrfTokenRepository(new CustomTokenRepository())
);
return http.build();
}
}
Common Token Repository Implementations
1. HttpSessionCsrfTokenRepository (default):
o Stores tokens in the HTTP session
o Expects token in header or parameter named _csrf
2. CookieCsrfTokenRepository:
o Stores token in a cookie named XSRF-TOKEN
o Client reads cookie and sends token in header X-XSRF-TOKEN
Token Generation Process Details
The actual token generation happens in the CsrfToken interface
implementations. The default implementation:
1. Uses SecureRandom for cryptographically strong random numbers
2. Base64 encodes the random bytes
3. Creates a token with:
o A random value (the actual token string)
o A parameter name (default _csrf)
o A header name (default X-CSRF-TOKEN)
Example Flow
1. First GET request:
o Server generates token, stores in session
o Includes token in response (form field or header)
2. Subsequent POST request:
o Client includes token in request
o Server validates token against session-stored token
o If valid, processes request; if not, returns 403
This mechanism ensures that only requests originating from your own site can
perform state-changing operations.
Setting up CSRF protection in a Spring Boot backend with an Angular
frontend:
Spring Boot Configuration (Backend)
1. Basic Security Configuration:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.formLogin(withDefaults())
.httpBasic(withDefaults());
return http.build();
2. CORS Configuration (for Angular dev server):
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("*")
.allowCredentials(true);
};
Angular Configuration (Frontend)
1. HttpInterceptor for CSRF:
// csrf.interceptor.ts
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from
'@angular/common/http';
import { Observable } from 'rxjs';
export class CsrfInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
// Only add CSRF for non-GET requests to your API
if (!req.method.includes('GET') && req.url.includes('/api/')) {
const csrfToken = this.getCookie('XSRF-TOKEN');
if (csrfToken) {
req = req.clone({
withCredentials: true,
setHeaders: {
'X-XSRF-TOKEN': csrfToken
});
return next.handle(req);
private getCookie(name: string): string | null {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]
+)'));
return match ? match[2] : null;
}
}
2. Register the Interceptor:
// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/core';
import { CsrfInterceptor } from './csrf.interceptor';
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: CsrfInterceptor, multi: true }
})
export class AppModule {}
3. Service Example:
// data.service.ts
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class DataService {
constructor(private http: HttpClient) {}
postData(data: any) {
return this.http.post('/api/data', data, {
withCredentials: true
});
Key Points:
1. Token Flow:
o Spring sends XSRF-TOKEN cookie (automatically
via CookieCsrfTokenRepository)
o Angular reads cookie and sends back as X-XSRF-TOKEN header
2. Important Headers:
Cookie: XSRF-TOKEN=abc123
X-XSRF-TOKEN: abc123
3. Security Considerations:
o Ensure withCredentials: true in Angular requests
o Configure CORS properly on backend
o Keep HttpOnly=false for the CSRF cookie (so Angular can read it)
4. Testing Endpoint (Spring Boot):
@RestController
public class TestController {
@GetMapping("/api/csrf")
public String getCsrf() {
return "CSRF protected endpoint";
}
@PostMapping("/api/test")
public String testPost() {
return "POST successful";
Verification Steps:
1. Check browser cookies after first GET request - should see XSRF-TOKEN
2. Inspect POST requests in DevTools - should include both:
o Cookie header with XSRF-TOKEN
o X-XSRF-TOKEN header
3. Try removing the token header - should get 403 Forbidden
Spring Boot Backend Configuration
1. Spring Security Setup
Configure Spring Security to use CookieCsrfTokenRepository with HttpOnly set
to false, allowing JavaScript to access the CSRF token stored in a cookie:
java
CopyEdit
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;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()
)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").permitAll()
.anyRequest().authenticated()
.httpBasic();
return http.build();
This configuration ensures that:
CSRF tokens are stored in cookies accessible to JavaScript.
API endpoints under /api/** are publicly accessible.
All other endpoints require authentication.
2. Serving the Angular Application
If you're serving the Angular frontend through Spring Boot, ensure that your
controller maps to the appropriate route:
java
CopyEdit
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class FrontendController {
@GetMapping("/")
public String index() {
return "index.html";
🌐 Angular Frontend Configuration
1. HTTP Interceptor for CSRF Token
Create an HTTP interceptor to read the CSRF token from the cookie and include
it in the headers of HTTP requests:
typescript
CopyEdit
// csrf.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from
'@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class CsrfInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
const csrfToken = this.getCookie('XSRF-TOKEN');
if (csrfToken) {
req = req.clone({
setHeaders: {
'X-XSRF-TOKEN': csrfToken
});
return next.handle(req);
private getCookie(name: string): string | null {
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const [k, v] = cookie.trim().split('=');
if (k === name) return decodeURIComponent(v);
return null;
2. Registering the Interceptor
In your app.module.ts, register the interceptor:
typescript
CopyEdit
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { CsrfInterceptor } from './csrf.interceptor';
@NgModule({
...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: CsrfInterceptor, multi: true }
})
export class AppModule { }
3. Making HTTP Requests
With the interceptor in place, you can make HTTP requests as usual, and the
CSRF token will be automatically included:
typescript
CopyEdit
this.http.post('/api/save-data', { name: 'Test' }).subscribe(response => {
console.log('Saved:', response);
});
🔄 Workflow Summary
1. Initial Request: When the Angular application loads, it makes an initial
request to the Spring Boot backend.
2. CSRF Token Generation: Spring Security generates a CSRF token and
stores it in a cookie named XSRF-TOKEN.
3. Token Retrieval: The Angular interceptor reads the XSRF-TOKEN cookie
and includes its value in the X-XSRF-TOKEN header of subsequent HTTP
requests.
4. Token Validation: Spring Security validates the CSRF token from the
header against the one stored in the cookie. If they match, the request is
processed; otherwise, it's rejected.
📚 Reference Implementation
For a practical example, you can refer to the following GitHub repository:
Spring Security and Angular CSRF Example: This project
demonstrates login, logout, CSRF protection, and authentication guards
using Spring Security 6 and Angular. GitHubGitHub+1YouTube+1
Here's a comprehensive guide to setting up CSRF protection in a Spring Boot
backend with an Angular frontend:
Spring Boot Configuration (Backend)
1. Basic Security Configuration:
java
Copy
Download
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()
)
.formLogin(withDefaults())
.httpBasic(withDefaults());
return http.build();
2. CORS Configuration (for Angular dev server):
java
Copy
Download
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("*")
.allowCredentials(true);
};
Angular Configuration (Frontend)
1. HttpInterceptor for CSRF:
typescript
Copy
Download
// csrf.interceptor.ts
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from
'@angular/common/http';
import { Observable } from 'rxjs';
export class CsrfInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
// Only add CSRF for non-GET requests to your API
if (!req.method.includes('GET') && req.url.includes('/api/')) {
const csrfToken = this.getCookie('XSRF-TOKEN');
if (csrfToken) {
req = req.clone({
withCredentials: true,
setHeaders: {
'X-XSRF-TOKEN': csrfToken
});
}
}
return next.handle(req);
private getCookie(name: string): string | null {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]
+)'));
return match ? match[2] : null;
2. Register the Interceptor:
typescript
Copy
Download
// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/core';
import { CsrfInterceptor } from './csrf.interceptor';
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: CsrfInterceptor, multi: true }
})
export class AppModule {}
3. Service Example:
typescript
Copy
Download
// data.service.ts
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class DataService {
constructor(private http: HttpClient) {}
postData(data: any) {
return this.http.post('/api/data', data, {
withCredentials: true
});
Key Points:
1. Token Flow:
o Spring sends XSRF-TOKEN cookie (automatically
via CookieCsrfTokenRepository)
o Angular reads cookie and sends back as X-XSRF-TOKEN header
2. Important Headers:
Copy
Download
Cookie: XSRF-TOKEN=abc123
X-XSRF-TOKEN: abc123
3. Security Considerations:
o Ensure withCredentials: true in Angular requests
o Configure CORS properly on backend
o Keep HttpOnly=false for the CSRF cookie (so Angular can read it)
4. Testing Endpoint (Spring Boot):
java
Copy
Download
@RestController
public class TestController {
@GetMapping("/api/csrf")
public String getCsrf() {
return "CSRF protected endpoint";
@PostMapping("/api/test")
public String testPost() {
return "POST successful";
Verification Steps:
1. Check browser cookies after first GET request - should see XSRF-TOKEN
2. Inspect POST requests in DevTools - should include both:
o Cookie header with XSRF-TOKEN
o X-XSRF-TOKEN header
3. Try removing the token header - should get 403 Forbidden
Would you like me to provide:
1. A complete GitHub repository with this setup?
2. Additional security configurations (like JWT + CSRF combo)?
3. Testing strategies for CSRF protection?
A complete GitHub repository with this setup
I'll outline a complete GitHub repository structure for a Spring Boot + Angular
application with CSRF protection. You can find a ready-to-use implementation
here:
GitHub Repository:
🔗 https://github.com/example/spring-boot-angular-csrf-demo (Note: This is a
placeholder link - see below for actual implementation steps)
Repository Structure
Copy
Download
spring-boot-angular-csrf-demo/
├── backend/ # Spring Boot application
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/demo/
│ │ │ │ ├── config/
│ │ │ │ │ └── SecurityConfig.java
│ │ │ │ ├── controller/
│ │ │ │ │ └── ApiController.java
│ │ │ │ └── DemoApplication.java
│ │ │ └── resources/
│ │ │ └── application.properties
│ │ └── test/
│ └── pom.xml
├── frontend/ # Angular application
│ ├── src/
│ │ ├── app/
│ │ │ ├── services/
│ │ │ │ └── api.service.ts
│ │ │ ├── interceptors/
│ │ │ │ └── csrf.interceptor.ts
│ │ │ └── app.module.ts
│ │ ├── assets/
│ │ ├── environments/
│ │ └── index.html
│ ├── angular.json
│ └── package.json
└── README.md
How to Set This Up Locally
1. Backend (Spring Boot):
bash
Copy
Download
# Generate new Spring Boot project
spring init --dependencies=web,security backend
2. Frontend (Angular):
bash
Copy
Download
# Create new Angular app
ng new frontend
cd frontend
ng generate interceptor interceptors/csrf
Key Files Implementation
1. Spring Boot Security
Config (backend/src/main/java/.../SecurityConfig.java):
java
Copy
Download
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()
)
.ignoringRequestMatchers("/api/public/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.cors(cors -> cors.configurationSource(corsConfigurationSource()));
return http.build();
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE"));
configuration.setAllowCredentials(true);
configuration.addExposedHeader("X-XSRF-TOKEN");
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
2. Angular CSRF
Interceptor (frontend/src/app/interceptors/csrf.interceptor.ts):
typescript
Copy
Download
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class CsrfInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<unknown>, next: HttpHandler):
Observable<HttpEvent<unknown>> {
const token = this.getCookie('XSRF-TOKEN');
const isApiRequest = request.url.startsWith('/api');
const isMutatingRequest = ['POST', 'PUT', 'DELETE',
'PATCH'].includes(request.method);
if (isApiRequest && isMutatingRequest && token) {
request = request.clone({
withCredentials: true,
setHeaders: {
'X-XSRF-TOKEN': token
});
}
return next.handle(request);
private getCookie(name: string): string | null {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]
+)'));
return match ? decodeURIComponent(match[2]) : null;
Testing the Implementation
1. Start both applications:
bash
Copy
Download
# In backend directory
./mvnw spring-boot:run
# In frontend directory
ng serve
2. Verify CSRF protection:
Open DevTools and check:
o Cookies tab for XSRF-TOKEN
o Network tab for X-XSRF-TOKEN header in POST requests