skip to content

How do allowedOrigins, allowedOriginPatterns, and allowCredentials interact, and what is the wildcard-plus-credentials pitfall?

level: seniorimportance: must knowfreq 60%

answer

  1. + credentials = browser blocks it
  2. setAllowedOriginPatterns reflects concrete origin
  3. allowCredentials=true forbids literal *
  4. Vary: Origin for caches
  5. never reflect arbitrary Origin with credentials

basics

~20 s

If 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 s

With 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
java
@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

for a junior

May know origins must be listed but likely unaware of the credentials interaction.

for a middle

Knows * plus credentials is disallowed but may not know allowedOriginPatterns as the fix.

for a senior

Must articulate the pattern-reflects-concrete-origin mechanism, the IllegalArgumentException guard, and the security rationale.

for a principal

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

context