Cross-Origin APIs and Security: A Deep Dive into CORS, CSRF, and XSS Protection
Web applications today rely heavily on APIs that serve multiple origins, but this convenience comes with significant security considerations. Even with robust authentication and HTTPS encryption, unrestricted cross-origin API access can introduce vulnerabilities that developers must carefully address. This deep dive explores the security implications of CORS configurations and provides comprehensive strategies for risk mitigation.
Background: The Cross-Origin Security Landscape
Cross-Origin Resource Sharing (CORS) was introduced to enable controlled access to resources across different domains, relaxing the browser's same-origin policy in a secure manner. However, when APIs are configured to allow all origins (Access-Control-Allow-Origin: *), they essentially become publicly accessible endpoints that can be called from any website.
The question many developers face is: if we have comprehensive authentication and use HTTPS, how significant are the remaining security risks?
Core Security Concepts and Attack Vectors
1. Cross-Site Request Forgery (CSRF)
CSRF attacks exploit the browser's automatic inclusion of credentials (cookies, authentication headers) in cross-origin requests. Even with proper authentication, unrestricted CORS can enable these attacks.
Attack Flow:
1. User logs into legitimate-app.com (receives auth cookie)
2. User visits malicious-site.com while still logged in
3. Malicious site sends request to legitimate-app.com/api/transfer-money
4. Browser automatically includes auth cookie
5. API processes request as authenticated user
Mitigation Strategies:
// Spring Security CSRF Protection
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/api/public/**")
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.build();
}
}For Cookie-based authentication, implement SameSite restrictions:
@Bean
public CookieSameSiteSupplier cookieSameSiteSupplier() {
return CookieSameSiteSupplier.of(SameSite.STRICT);
}2. Cross-Site Scripting (XSS) Amplification
While XSS primarily affects the frontend, unrestricted CORS can amplify the impact by allowing malicious scripts to interact with APIs from any origin.
Frontend Protection (Vue.js Example):
<template>
<!-- Safe: Vue automatically escapes content -->
<div>{{ userInput }}</div>
<!-- Dangerous: Raw HTML injection -->
<div v-html="sanitizeHtml(userInput)"></div>
</template>
<script>
import DOMPurify from 'dompurify';
export default {
methods: {
sanitizeHtml(input) {
// Always sanitize before using v-html
return DOMPurify.sanitize(input);
}
}
}
</script>Backend Protection:
@Component
public class XSSProtectionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Set Content Security Policy
httpResponse.setHeader("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; img-src 'self' data: https:");
// Prevent XSS in legacy browsers
httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
chain.doFilter(request, response);
}
}3. Token Exposure and Credential Leakage
When APIs lack origin restrictions, there's increased risk of credentials being exposed in unintended contexts.
Problem Scenario:
// Frontend accidentally sends auth tokens to public APIs
fetch('https://public-api.example.com/data', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('authToken')}`, // Unnecessary!
'Content-Type': 'application/json'
}
});Secure Implementation:
class ApiClient {
constructor(baseUrl, requiresAuth = false) {
this.baseUrl = baseUrl;
this.requiresAuth = requiresAuth;
}
async request(endpoint, options = {}) {
const headers = {
'Content-Type': 'application/json',
...options.headers
};
// Only add auth headers when required
if (this.requiresAuth) {
const token = localStorage.getItem('authToken');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
}
return fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers
});
}
}
// Usage
const publicApi = new ApiClient('https://public-api.com', false);
const privateApi = new ApiClient('https://private-api.com', true);Risk Analysis and Mitigation Framework
JWT Authentication vs Cookie-Based Authentication
The security implications of unrestricted CORS vary significantly based on your authentication mechanism:
JWT with Authorization Headers (Low CSRF Risk):
// Browsers don't automatically send Authorization headers cross-origin
const response = await fetch('https://api.example.com/protected', {
method: 'POST',
headers: {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});Since browsers don't automatically include Authorization headers in cross-origin requests, CSRF attacks cannot leverage JWT tokens stored in localStorage or sessionStorage.
Cookie-Based Authentication (Higher CSRF Risk):
@RestController
public class SecureController {
@PostMapping("/api/sensitive-action")
public ResponseEntity<?> sensitiveAction(HttpServletRequest request) {
// Verify origin for cookie-based auth
String origin = request.getHeader("Origin");
String referer = request.getHeader("Referer");
if (!isValidOrigin(origin) || !isValidReferer(referer)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
// Process request...
return ResponseEntity.ok().build();
}
private boolean isValidOrigin(String origin) {
return Arrays.asList("https://trusted-domain.com", "https://app.company.com")
.contains(origin);
}
}Comprehensive Security Configuration
Here's a complete Spring Boot security configuration that addresses the discussed vulnerabilities:
@Configuration
@EnableWebSecurity
public class ComprehensiveSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/api/public/**")
)
.headers(headers -> headers
.contentSecurityPolicy("default-src 'self'; script-src 'self'")
.and()
.httpStrictTransportSecurity(hsts -> hsts
.maxAgeInSeconds(31536000)
.includeSubdomains(true)
)
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
)
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Restrict to trusted origins instead of allowing all
configuration.setAllowedOriginPatterns(Arrays.asList(
"https://*.company.com",
"https://trusted-partner.com"
));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}Implications and Best Practices
When Risks Are Controllable
The security risks of unrestricted CORS become manageable when you implement:
- CSRF Protection: SameSite cookies, CSRF tokens, or origin validation
- XSS Prevention: Input sanitization, output encoding, and CSP headers
- Secure Token Management: JWT via Authorization headers instead of cookies
- Rate Limiting: Prevent API abuse and DoS attacks
- Comprehensive Logging: Monitor for suspicious cross-origin activity
Risk Assessment Matrix
| Security Control | CSRF Risk | XSS Risk | Token Exposure | Overall Risk |
|---|---|---|---|---|
| No CORS restrictions + Cookie auth | High | Medium | Medium | High |
| No CORS restrictions + JWT (localStorage) | Low | High | Medium | Medium |
| Restricted CORS + Cookie auth | Low | Low | Low | Low |
| Restricted CORS + JWT + CSP | Very Low | Very Low | Very Low | Very Low |
AWS and Cloud-Level Protections
While application-level security is primary, cloud platforms can provide additional layers:
# AWS WAF Rule Example
Rules:
- Name: RestrictOriginRule
Priority: 1
Statement:
ByteMatchStatement:
SearchString: "malicious-domain.com"
FieldToMatch:
SingleHeader:
Name: "origin"
TextTransformations:
- Priority: 0
Type: LOWERCASE
Action:
Block: {}AWS CloudFront Security Headers:
// Lambda@Edge function for security headers
exports.handler = (event, context, callback) => {
const response = event.Records[0].cf.response;
const headers = response.headers;
headers['content-security-policy'] = [{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline'"
}];
headers['x-frame-options'] = [{
key: 'X-Frame-Options',
value: 'DENY'
}];
callback(null, response);
};Conclusion
While unrestricted CORS policies in APIs pose legitimate security risks, these risks become highly manageable with proper implementation of layered security controls. The key is not to rely on CORS restrictions alone, but to implement a comprehensive security strategy that includes:
- Authentication-appropriate CSRF protection
- Multi-layered XSS prevention (both frontend and backend)
- Secure credential management practices
- Comprehensive monitoring and logging
For modern applications using JWT authentication with Authorization headers, the CSRF risk is naturally mitigated, but XSS and token exposure concerns remain. The most secure approach combines restricted CORS policies with robust application-level security controls, creating defense in depth that protects against both current and emerging attack vectors
