CSRF, CORS and security headers
CSRF (cross-site request forgery): browsers attach cookies to every request for a site, even when another site triggers it. If you authenticate with cookies, a malicious page can make your browser send authenticated requests.
- Spring Security enables CSRF protection by default: state-changing requests (POST, PUT, DELETE) must include a secret CSRF token that only your own pages know.
- Server-rendered forms (Thymeleaf) include it automatically. Single-page apps read it from an XSRF-TOKEN cookie and send it back as an X-XSRF-TOKEN header; Angular's HttpClient does this out of the box.
- SameSite cookies (Lax or Strict) add a second layer.
- APIs that authenticate with an
Authorizationheader (no cookies) aren't vulnerable, which is the only good reason to disable CSRF.
CORS (cross-origin resource sharing): a browser rule that stops a page from reading responses from another origin unless that origin allows it. It protects users; it is not an access control for your API (curl ignores it). Configure exact allowed origins; * can't be combined with credentials.
Security headers: Spring adds sensible ones by default (X-Content-Type-Options, X-Frame-Options, cache control for authenticated pages, and HSTS over HTTPS). Add a Content-Security-Policy to limit where scripts can load from.
How a CORS preflight works
For cross-origin requests that aren't "simple" (JSON bodies, an Authorization header, PUT or DELETE), the browser first sends an OPTIONS preflight asking what is allowed, and only then the real request. The response must carry Access-Control-Allow-Origin for the browser to hand it to the page. Step through it below.
The security headers Spring adds
X-Content-Type-Options: nosniff: browsers must not guess content types.X-Frame-Options: DENY: your pages can't be framed (clickjacking).Cache-Control: no-cache, no-store: authenticated responses aren't cached.Strict-Transport-Security(HTTPS only): browsers use HTTPS for your site from now on.- Not added by default, and worth adding:
Content-Security-Policy, which limits where scripts, styles and frames may come from and is your strongest defence against XSS.
Example
@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
return http
.cors(Customizer.withDefaults()) // uses the CorsConfigurationSource bean
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // XSRF-TOKEN cookie for the SPA
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()))
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'; frame-ancestors 'none'"))
.frameOptions(frame -> frame.deny()))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration cfg = new CorsConfiguration();
cfg.setAllowedOrigins(List.of("https://javaatlas.com")); // exact origins, never "*" with credentials
cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
cfg.setAllowedHeaders(List.of("Content-Type", "Authorization", "X-XSRF-TOKEN"));
cfg.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", cfg);
return source;
}Common mistake
Calling csrf().disable() on an app that logs users in with session cookies, or "fixing" a CORS error with allowedOrigins("*").
Under the hood
If your frontend and API live on different sites (for example *.vercel.app and *.up.railway.app), cookies marked SameSite=Strict or Lax won't be sent at all, and third-party cookie blocking makes SameSite=None unreliable. Putting both under one site (javaatlas.com and api.javaatlas.com) fixes it, which is exactly why this site's deployment uses its own domains.
Check yourself
Which kind of authentication is vulnerable to CSRF?
How this connects
Know these first
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.