How do allowedOrigins, allowedOriginPatterns, and allowCredentials interact, and what is the wildcard-plus-credentials pitfall?
answer
- + credentials = browser blocks it
- setAllowedOriginPatterns reflects concrete origin
- allowCredentials=true forbids literal *
- Vary: Origin for caches
- never reflect arbitrary Origin with credentials
basics
~20 sIf allowCredentials is true you cannot use "*" for origins, because the browser refuses Access-Control-Allow-Origin: * together with credentials. Either list exact origins with setAllowedOrigins, or use setAllowedOriginPatterns to match wildcards while still reflecting a concrete origin.
solid answer
~40 sWith CORS, Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true are mutually exclusive — browsers block responses that combine them, so cookie/Authorization-based cross-origin calls fail. setAllowedOrigins("*") emits the literal wildcard, which is fine only for anonymous APIs. When you need credentials, you must echo a specific origin. Spring 5.3+ added CorsConfiguration.setAllowedOriginPatterns, which lets you write patterns like https://*.example.com; Spring matches the request Origin against the pattern and reflects the concrete origin back in Access-Control-Allow-Origin, so credentials remain legal. setAllowCredentials(true) also makes Spring validate that you did not use a literal "*". Practically: setAllowedOriginPatterns for flexible subdomain matching with credentials, setAllowedOrigins for an explicit allowlist, and never combine literal "*" with credentials.
code
java · 19 lines@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
// Credentials needed (cookies / Authorization) -> must NOT use literal "*".
config.setAllowCredentials(true);
// Wrong: throws IllegalArgumentException because "*" + credentials is illegal.
// config.setAllowedOrigins(List.of("*"));
// Right: pattern is matched and the concrete origin is reflected back.
config.setAllowedOriginPatterns(List.of("https://*.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}go deeper
May know origins must be listed but likely unaware of the credentials interaction.
Knows * plus credentials is disallowed but may not know allowedOriginPatterns as the fix.
Must articulate the pattern-reflects-concrete-origin mechanism, the IllegalArgumentException guard, and the security rationale.
Weighs allowlist vs pattern vs dynamic environments and treats blind Origin reflection as a vulnerability class.
## The three knobs On `CorsConfiguration`: - `setAllowedOrigins(List<String>)` — an exact allowlist. Values are compared for exact string equality against the request `Origin`. The special value `"*"` becomes the literal `Access-Control-Allow-Origin: *` header. - `setAllowedOriginPatterns(List<String>)` — added in Spring Framework 5.3. Values may contain wildcards (`https://*.example.com`, `https://app.example.com:[*]`). Spring matches the incoming `Origin` against the pattern and, on a match, **reflects the concrete origin** back rather than emitting `*`. - `setAllowCredentials(boolean)` — when `true`, adds `Access-Control-Allow-Credentials: true`, which is what makes the browser send and expose cookies / `Authorization` on cross-origin requests. ## The wildcard-plus-credentials rule The CORS spec forbids the combination of `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true`. If a server sends both, the **browser discards the response** and the fetch fails. The rationale: `*` means "any site may read this," and combining that with credentials would let *any* website make authenticated calls as the logged-in user and read the result — a catastrophic leak. So you cannot have both a literal `*` origin and credentials. To support credentials you must return a **specific** origin. ## Why setAllowedOriginPatterns exists Before 5.3, supporting many origins (e.g. every subdomain) *with credentials* meant maintaining an exact list or writing a custom filter, because `*` was illegal with credentials. `setAllowedOriginPatterns` solves this: you specify a pattern, Spring validates the actual `Origin` against it, and when it matches, Spring writes that **exact** origin into `Access-Control-Allow-Origin` (not `*`). Because a concrete origin is returned, `Access-Control-Allow-Credentials: true` is legal. ```java config.setAllowedOriginPatterns(List.of("https://*.example.com")); config.setAllowCredentials(true); ``` ## Validation Spring performs When `allowCredentials` is `true`, `CorsConfiguration` guards against the footgun: if you also set a literal `"*"` in `allowedOrigins` (or `"*"` allowedHeaders / allowedMethods depending on version), Spring throws an `IllegalArgumentException` at request-processing time (in `checkOrigin`/validation) rather than silently emitting an invalid header. The message tells you to use `allowedOriginPatterns` instead. ## The Vary: Origin implication Because reflecting a specific origin means the response differs per requesting origin, Spring adds `Vary: Origin` (along with `Vary: Access-Control-Request-Method` / `-Headers`) so that shared HTTP caches don't serve one origin's allow-header to another origin. This matters when a CDN/proxy caches API responses. ## Practical guidance - **Public, anonymous API, no cookies/tokens read cross-origin:** `setAllowedOrigins(List.of("*"))`, `allowCredentials` left false. Simple and safe. - **Authenticated SPA on a known origin:** `setAllowedOrigins(List.of("https://app.example.com"))`, `setAllowCredentials(true)`. - **Authenticated multi-subdomain / dynamic preview environments:** `setAllowedOriginPatterns(List.of("https://*.example.com"))`, `setAllowCredentials(true)`. - **Never** reflect arbitrary origins unconditionally with credentials — that recreates the `*`+credentials vulnerability manually. ## Gotcha: reflecting Origin blindly A custom implementation that copies the request `Origin` straight into `Access-Control-Allow-Origin` with credentials true is effectively `*`+credentials and lets any site make authenticated requests. Always constrain with an allowlist or a tight pattern.
- Why is Access-Control-Allow-Origin: * combined with credentials a security risk, not just a spec quirk?It would let any website on the internet make authenticated requests using the victim's cookies/token and read the response, exfiltrating their data. The browser therefore refuses the combination outright.
- When Spring uses allowedOriginPatterns, what value ends up in the Access-Control-Allow-Origin response header?The concrete matched request origin (e.g. https://team1.example.com), not the pattern and not *, which keeps it compatible with Access-Control-Allow-Credentials: true.
saying these in an interview costs you the question
- Saying setAllowedOrigins("*") works fine with allowCredentials(true)
- Confusing allowedOrigins (exact match) with allowedOriginPatterns (wildcard match)
- Reflecting the request Origin unconditionally with credentials enabled