Spring Security
CORS Configuration — Allowing Cross-Origin Requests Safely
Structuring cross-origin sharing configurations for web browsers safely.
Introduction
Browsers implement the Same-Origin Policy (SOP) to block scripts on one domain from accessing data on another. **CORS (Cross-Origin Resource Sharing)** is a browser mechanism that defines safe rules for allowing cross-domain communication.
Why It Matters
Modern web architectures decouple frontend applications from backend APIs. Without CORS configuration, frontends cannot query backend endpoints. However, a loose CORS configuration (such as allowing all origins via wildcards) opens up users to CSRF attacks or data leakage.
Real-World Analogy
Think of CORS like embassy passport controls. A script from Domain A (a traveler) wants to visit Domain B (the target country). The browser (border guard) stops the script and asks Domain B: "Do you allow travelers from Domain A to enter?" Domain B must return official approval documents (CORS headers) specifying allowed origins, or the browser blocks the script from entering.
Detailed Mechanics
1. CORS Preflight OPTIONS Requests
For requests that modify database data (such as POST with JSON, PUT, or DELETE), browsers send a **Preflight Request** using the OPTIONS method first. This preflight query checks if the target API permits the origin, methods, and headers of the planned call. If the preflight succeeds, the browser sends the actual request.
2. Spring Security CORS Filter
In Spring applications, CORS must be processed at the earliest entry point. Because Spring Security filters execute before MVC controllers, configuring CORS in MVC (e.g. using @CrossOrigin) can fail because Spring Security's authorization filters intercept preflight OPTIONS queries and reject them if they lack authentication headers. CORS should be configured as a servlet Filter inside the security chain.
3. Safe Production Settings
| Attribute | Wildcard (*) | Production Recommendation |
|---|---|---|
| allowedOrigins | Allows all domains (Insecure) | List specific domains (e.g., https://app.company.com) |
| allowCredentials | Cannot be true with origin wildcard | Set to true only for trusted origins |
| maxAge | 0 seconds (Browser queries preflight every time) | 3600 seconds (Caches preflight, improves speed) |
Practical Example
Here is how to configure a CORS filter bean within your Spring Security configuration:
Quick Quiz
Q1: Which HTTP method is executed by web browsers as a preflight CORS validation query?
A) GET
B) POST
C) OPTIONS
D) HEAD
Answer: C — Browsers send OPTIONS requests with CORS metadata headers to confirm path accessibility before firing actual mutating calls.
Q2: Why should CORS be configured in the Spring Security filter chain layer rather than using MVC interceptors?
A) MVC interceptors do not support HTTP OPTIONS checks.
B) Spring Security runs before Spring MVC; security filters would block preflight OPTIONS requests before MVC CORS interceptors can run.
C) Filter configurations run faster than controller mappings.
D) WebMvcConfigurer is deprecated in Spring Boot 3.
Answer: B — Security filters take precedence. Unless CORS is handled early in the filter chain, OPTIONS requests fail authorization audits.
Scenario-Based Challenge
Production Scenario:
You deploy your backend to api.company.com and React frontend to company.com. In your Spring configuration, you declare: configuration.setAllowedOrigins(List.of("https://company.com")). However, the browser blocks request payloads, reporting: "The Access-Control-Allow-Origin header contains multiple values, but only one is allowed". Why did this happen?
This happens when CORS has been configured in **multiple places** (e.g., configuring CORS both in Spring Security and also defining a global WebMvcConfigurer or using a cloud proxy/reverse gateway like NGINX that injects the CORS header). The browser receives duplicate Access-Control-Allow-Origin headers and rejects the response.
To fix this:
- Verify configuration files: ensure CORS is only configured once, preferably in the
SecurityFilterChainbean definition. - Check proxy server settings: if NGINX or AWS API Gateway is handling CORS, disable CORS configurations in the Spring Boot application codebase entirely to avoid header duplication.
Interview Questions
1. Conceptual: Why is it invalid to set allowedOrigins to "*" while setting allowCredentials to true?
This is prohibited by W3C browser security specifications. If credential transmission (cookies, Basic Auth, client certs) is permitted, allowing wildcard origins would enable any malicious website to read the victim's private data, defeating Same-Origin Policy protections.
2. Concept: What is the "Access-Control-Max-Age" header and why is it useful?
It specifies the duration (in seconds) that the preflight OPTIONS response can be cached by client browsers. Caching this response prevents the browser from sending redundant OPTIONS requests before every actual API call, reducing latency.
Production Considerations
Do not use wildcards in CORS properties. Set allowedOrigins, allowedMethods, and allowedHeaders explicitly. Configure a reasonable caching limit using maxAge to optimize client performance.