skip to content

What is the interaction between allowCredentials(true) and allowedOrigins("*"), and how do you allow credentials with multiple/dynamic origins?

level: seniorimportance: must knowfreq 62%

answer

  1. credentials + Allow-Origin:* = forbidden by browser
  2. Spring throws IllegalArgumentException at startup
  3. use allowedOriginPatterns(...) — echoes exact origin + Vary:Origin
  4. no literal '*' for headers under credentials
  5. never reflect Origin blindly with credentials

basics

~20 s

You cannot combine allowCredentials(true) with allowedOrigins("*") — the browser forbids credentials when Access-Control-Allow-Origin is the wildcard, and recent Spring throws at startup. Use allowedOriginPatterns(...) (or list exact origins) so Spring echoes the specific origin back.

solid answer

~40 s

The CORS spec forbids credentialed requests when Access-Control-Allow-Origin is '*': the browser rejects the response. So allowCredentials(true) with allowedOrigins("*") is illegal, and Spring 5.3+/6 fails fast at startup with an IllegalArgumentException rather than silently misbehaving. To support credentials you must send a specific origin. Options: (1) list exact origins with allowedOrigins("https://a.com", "https://b.com") — Spring echoes back the matching one; (2) for wildcards/subdomains use allowedOriginPatterns("https://*.example.com"), which Spring introduced precisely so pattern matching can coexist with credentials — it matches the incoming Origin and echoes that exact value into Access-Control-Allow-Origin, plus Vary: Origin. Also, with credentials you cannot use allowedHeaders("*")/exposedHeaders("*") as literal wildcards — you must enumerate headers. Credentials means cookies, TLS client certs, and the Authorization header flow cross-origin, so this must be scoped to trusted origins only.

code

java · 14 lines
java
@Configuration
class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            // allowedOrigins("*") + allowCredentials(true) would throw at startup.
            .allowedOriginPatterns("https://*.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("Authorization", "Content-Type") // enumerate, not "*"
            .exposedHeaders("X-Total-Count")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

go deeper

for a junior

Know that '*' and credentials can't be combined.

for a middle

Know the fix is allowedOriginPatterns or explicit origins and that Spring fails fast.

for a senior

Explain origin echoing + Vary:Origin, header-wildcard limits, and the reflect-Origin anti-pattern.

for a principal

Own the trust boundary: credentialed CORS is a security control; enforce a tight, reviewed first-party allowlist and reason about multi-tenant dynamic origins via CorsConfigurationSource.

## What 'credentials' means In CORS, **credentials** = cookies, HTTP authentication (including the `Authorization` header in the credentialed mode), and TLS client certificates being **sent on** and **readable for** a cross-origin request. On the client this is `fetch(url, { credentials: 'include' })` or XHR `withCredentials = true`. On the server, you opt in with `Access-Control-Allow-Credentials: true` (Spring: `allowCredentials(true)`). ## The wildcard rule The CORS spec has a hard rule: **when credentials are involved, `Access-Control-Allow-Origin` may not be `*`.** It must be an **exact origin** string. Likewise `Access-Control-Allow-Headers` and `-Expose-Headers` cannot be `*` (the literal wildcard) in credentialed responses — the wildcard is treated as the literal header name `*`, not 'all'. The browser enforces this: a credentialed response with `Allow-Origin: *` is **rejected**, and JS can't read it. ### Spring fails fast Because this is a guaranteed-broken combination, **Spring 5.3+ / 6** validates it at configuration time. `CorsConfiguration.validateAllowCredentials()` throws an `IllegalArgumentException` at startup if `allowCredentials=true` while `allowedOrigins` contains `"*"`. This prevents the classic runtime-only bug where everything looks configured but the browser silently blocks responses. ## The fix: allowedOriginPatterns Spring introduced **`allowedOriginPatterns(...)`** (Spring 5.3) specifically for this. Instead of a static `Access-Control-Allow-Origin` value, patterns are **matched against the incoming `Origin`**, and when matched Spring **echoes the exact request origin** back into `Access-Control-Allow-Origin` (plus `Vary: Origin` so caches don't mix responses). Because a concrete origin is echoed — never the literal `*` — credentials are permitted. ```java registry.addMapping("/api/**") .allowedOriginPatterns("https://*.example.com") // NOT allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("Authorization", "Content-Type") .allowCredentials(true) .maxAge(3600); ``` ### Ways to support credentials + several origins 1. **Enumerate exact origins**: `allowedOrigins("https://app.example.com", "https://admin.example.com")` — Spring echoes the one that matches. 2. **Pattern matching**: `allowedOriginPatterns("https://*.example.com")` — for wildcard subdomains, ports, or dynamic tenants. 3. **Custom `CorsConfigurationSource`**: compute allowed origins at runtime (e.g. from a DB of tenant domains) and return a `CorsConfiguration` with the resolved exact origin. ## Header wildcards under credentials With `allowCredentials(true)` you must **enumerate** `allowedHeaders` and `exposedHeaders`; `"*"` won't function as 'all' in the response. Practically: list `Authorization`, `Content-Type`, and whatever custom headers you use. ## Security implications Allowing credentials cross-origin means another origin's page can make authenticated calls as the logged-in user. Combined with a too-broad `allowedOriginPatterns` (e.g. `https://*`), that's effectively 'any HTTPS site can act as the user' — a serious CSRF-adjacent exposure. **Scope credentialed CORS to a tight allowlist of first-party origins.** Never reflect the request `Origin` unconditionally (the anti-pattern of `setAllowedOrigins(listOf(request.origin))` with `allowCredentials`), which is equivalent to `*` with credentials. ## Summary rule of thumb - Public, no cookies: `allowedOrigins("*")`, `allowCredentials(false)` is fine. - Cookie/Authorization SPA: exact origins or `allowedOriginPatterns`, `allowCredentials(true)`, enumerated headers. - Never: `allowedOrigins("*")` + `allowCredentials(true)` (Spring throws).

  • Your app starts fine with allowedOrigins("*") and allowCredentials(false), then you flip credentials to true and it crashes on boot. Why, and the fix?
    Spring's CorsConfiguration.validateAllowCredentials() throws because '*' with credentials is spec-illegal. Replace allowedOrigins("*") with allowedOriginPatterns(...) or explicit exact origins.
  • Why is reflecting the incoming Origin header back with allowCredentials(true) dangerous?
    It effectively allows every origin to make authenticated requests as the user — the same exposure as wildcard-with-credentials, enabling cross-site data theft. Always match against a fixed allowlist instead.

saying these in an interview costs you the question

  • Saying allowedOrigins("*") works with credentials if you just add the header
  • Using allowedHeaders("*") together with allowCredentials(true) and expecting 'all headers'
  • Reflecting request Origin unconditionally with credentials enabled
  • Not knowing allowedOriginPatterns exists / confusing it with allowedOrigins

context