Configuring CORS in Spring Security

CorsConfigurationSource Bean

The preflight has no Authorization header, so if Spring Security sees it first, it answers 401 and the browser reports a "CORS error". http.cors(...) adds a CorsFilter early in the chain that answers preflights before authentication runs.

import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    CorsConfigurationSource corsConfigurationSource(
            @Value("${shopease.cors.allowed-origins}") List<String> origins) {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(origins);                      // exact origins, no "*"
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-XSRF-TOKEN"));
        config.setExposedHeaders(List.of("Location"));
        config.setAllowCredentials(true);                       // needed for the refresh cookie
        config.setMaxAge(3600L);                                // cache the preflight for 1 hour

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

// in SecurityConfig
http.cors(Customizer.withDefaults());   // uses the CorsConfigurationSource bean above

Config

application.yml

shopease:
  cors:
    allowed-origins: http://localhost:5173,https://shopease.in

Testing a Preflight with curl

Example

Allowed origin vs unknown origin:

curl -i -X OPTIONS localhost:8080/api/orders \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: authorization,content-type"

curl -i -X OPTIONS localhost:8080/api/orders \
  -H "Origin: https://evil.example" \
  -H "Access-Control-Request-Method: POST"

Example output

HTTP/1.1 200 OK
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600

HTTP/1.1 403 Forbidden
Invalid CORS request

Common Mistakes

  • allowedOrigins("*") with allowCredentials(true): not allowed by browsers, and Spring throws an error. List exact origins (or use setAllowedOriginPatterns like https://*.shopease.in).
  • Configuring CORS only in a WebMvcConfigurer but not calling http.cors(...): preflights hit security first and fail with 401.
  • Forgetting withCredentials: true in axios: the browser silently does not send the refresh cookie.