A feed answers Access-Control-Allow-Headers with the wildcard, yet a caller sending Authorization still fails the check - why?
answer
- a wildcard is not always a wildcard
- one request-header name is carved out
- the carved-out one carries the credential
- credentials change every wildcard reading
- no way to grant a field named star
basics
~10 sAuthorization is the CORS non-wildcard request-header name: the wildcard in Access-Control-Allow-Headers covers other unsafe request headers but never that one, so a server must list Authorization by name for a caller that sends it.
solid answer
~40 sFor a request made **without** credentials, `*` in `Access-Control-Allow-Headers`, `Access-Control-Allow-Methods` and `Access-Control-Expose-Headers` is read as a wildcard rather than as a literal field name. One name is deliberately carved out of that: `Authorization` is the **CORS non-wildcard request-header name**, so a preflight whose answer is `Access-Control-Allow-Headers: *` still fails as soon as the caller's request carries `Authorization`. The fix is to name it: `Access-Control-Allow-Headers: Authorization, Content-Type`. Two corollaries follow. Because `*` is read as a wildcard there, no server can grant a field whose name is literally `*`. And every one of these wildcard readings changes in credentialed mode, which is a separate subject - so a wildcard is never a safe default to reason about without first establishing which mode the request is in.
code
http · 4 linesHTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://charts.example.net
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Authorization, Content-Typego deeper
The takeaway to recall is narrow: a wildcard header grant does not cover the header that carries a caller's credential, which must be named.
Explain which three grant fields accept a wildcard, that the reading applies to requests without credentials, and which single request-header name is excluded from it.
Show the diagnosis: anonymous callers fine, authenticated ones failing on the preflight, and a grant that looks more permissive than the working one. Compare what was asked for against what was named.
The position worth arguing is that a wildcard grant records no decision. Naming what a service grants keeps the surface reviewable as the number of callers grows.
This is the failure that survives a careful configuration review, because the grant looks maximally permissive and the call still fails. Reading the wildcard precisely is the whole answer. ## When `*` is a wildcard at all Three grant fields accept `*`: - `Access-Control-Allow-Methods` - any method, rather than a named list; - `Access-Control-Allow-Headers` - any CORS-unsafe request header name the caller wants to send; - `Access-Control-Expose-Headers` - every response header name becomes readable by script. That reading applies to a request made **without credentials**. A request in credentialed mode reads these fields differently, and that difference is its own subject; what matters here is that *the wildcard's meaning is conditional*, so an answer that says `*` means everything, full stop, has already overstated it. ## The one name the wildcard never covers `Authorization` is defined as the **CORS non-wildcard request-header name**. The preflight check treats it separately: if the request carries `Authorization` and the granted header names do not contain `Authorization` explicitly, the check fails - and `*` does not count as containing it. A gauge feed offering authenticated write access therefore cannot lean on the wildcard: ```http OPTIONS /stations/42/readings HTTP/1.1 Host: gauges.example.org Origin: https://charts.example.net Access-Control-Request-Method: PUT Access-Control-Request-Headers: authorization,content-type HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://charts.example.net Access-Control-Allow-Methods: PUT Access-Control-Allow-Headers: * ``` That exchange fails. Replacing the last line with `Access-Control-Allow-Headers: Authorization, Content-Type` makes it succeed, and nothing else in the exchange changes. The reason to carve it out is deliberate rather than accidental: a blanket `*` is nearly always written without any particular header in mind, and the one header most likely to carry a caller's credential should not be granted by an absent-minded default. Requiring it to be named makes the grant a decision. ## The corner the wildcard creates Because `*` is read as a wildcard in these fields, there is **no way to match a header or a method whose name is literally `*`**. The specification says so as an aside, and it is a fair question to be asked as a check on whether you have read the field or merely used it. In practice nothing is named `*`; the point is that the wildcard is a reading of the value, not an escape sequence with a literal form available. ## Diagnosing it The symptom is narrow and repeatable, and it separates this from every other CORS failure: 1. Anonymous callers of the same endpoint work. Authenticated ones do not. 2. The failure happens on the preflight, so the actual request is never issued and the server logs nothing for it. 3. The grant under review looks *more* permissive than a working one, which is why re-reading the configuration rarely finds it. | granted header field | caller sends Authorization | caller sends only a custom header | |---|---|---| | `*` | check fails | check passes | | `Authorization` | check passes | check fails | | `Authorization, X-Gauge-Station` | check passes | check passes | The habit that catches it is to compare what the caller actually asked for against what the answer actually named, rather than judging the answer's permissiveness in isolation. ## What this is not - **It is not about whether the header is safe to send.** The grant decides only whether the browser proceeds; the server still authenticates the caller on its own terms. - **It is not a carve-out on the response side.** `*` in `Access-Control-Expose-Headers` exposes every response header name for a non-credentialed request, with no equivalent exception - the exception is on the request-header grant. - **It is not an argument for the wildcard elsewhere.** A grant that names what it means is readable by the next person; a wildcard hides the fact that a decision was never made. State the rule the way it is checked - *`Authorization` must be named, because the wildcard does not include it* - and the rest of the field's behaviour follows.
- Why is that one request-header name carved out of the wildcard at all?Because a blanket wildcard is generally written without any specific header in mind, and the field most likely to carry a caller's credential should not be granted by default. Requiring it to be named turns the grant into a deliberate decision rather than a side effect of a permissive line.
- Does Access-Control-Expose-Headers have the same carve-out?No. The exception applies to the request-header grant. For a request made without credentials, `*` in `Access-Control-Expose-Headers` exposes every response header name to script with no named exception. Credentialed mode reads that field differently, which is a separate subject.
- Can a server grant a header whose field name is literally an asterisk?No. In these fields the character is read as a wildcard rather than as a name, and the specification notes there is therefore no way to match a header or method literally named that way. It is a corner of the grammar rather than a practical limitation.
saying these in an interview costs you the question
- Reads the wildcard in Access-Control-Allow-Headers as covering every header name
- Assumes the wildcard behaves identically whether or not credentials are sent
- Thinks Authorization is safelisted and needs no grant at all
- Blames the caller's header casing rather than the missing name
- Believes a field literally named with an asterisk can be granted by the wildcard