skip to content

What does a server grant when it answers a credentialed cross-origin request with `Access-Control-Allow-Credentials: True`?

level: middleimportance: should knowfreq 40%

answer

  1. one legal value, nothing else
  2. field names versus field values
  3. the grammar marks it case-sensitive
  4. no defined negative form exists
  5. server logs 200, script gets TypeError

basics

~20 s

Nothing at all. The field's value is defined as the byte sequence true, case-sensitively, so True is not a grant; the browser behaves exactly as if the field were absent and refuses script the response.

solid answer

~40 s

The field has one legal value and it is byte case-sensitive: `Access-Control-Allow-Credentials = %s"true"`. `True`, `TRUE`, `1` and `yes` are all simply not that value, so the CORS check fails for a request whose credentials mode is `"include"`, and the browser hands script a network error — a `TypeError` with `status 0`, no readable headers and no body. Meanwhile the server logged a perfectly ordinary `200` and has no idea anything went wrong. The trap is that HTTP field *names* are case-insensitive, so the instinct that values are too is a reasonable instinct and a wrong one here. There is also no negative form to send: refusing credentials is expressed by omitting the field.

code

http · 7 lines
http
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: https://portal.example
Access-Control-Allow-Credentials: True
Vary: Origin

{"requests":[{"id":114,"status":"open"}]}

go deeper

for a junior

Learn the one legal value: true, lower case. Any other spelling means the browser treats the response as ungranted.

for a middle

Explain why field names are case-insensitive while this value is not, and describe the network error script receives when the check fails.

for a senior

Show that you diagnose it from the asymmetry: a 200 in the server log against an error with no status in the client, which points at the grant value rather than the handler.

for a principal

The lesson to generalise is that grant fields are compared as bytes, so they belong in reviewed configuration data rather than being hand-typed per service.

## One value, and it is case-sensitive `Access-Control-Allow-Credentials` is defined with a grammar that admits exactly one value: ```http Access-Control-Allow-Credentials = %s"true" ``` The `%s` prefix in that grammar is the notation for a **case-sensitive** string. So the field carries the four bytes `true` and nothing else. `True` is a different byte sequence, and a conforming browser does not grant on it. This is unusual enough to be worth dwelling on. HTTP field **names** are matched without regard to case — `access-control-allow-credentials` and `Access-Control-Allow-Credentials` are the same field. Values are a different matter, decided field by field by that field's own grammar, and this one is explicit. ## What actually happens on the wire Walk the maintenance-request API of a tenant portal, where a developer has typed `True` into a response-header table: 1. The calling code opts the request into credentials mode `"include"`, so the browser attaches the tenant's cookie. 2. The request reaches the API. The API authenticates the tenant, builds the personalised list of open requests and answers `200` with the correct body and the header block it was configured with. 3. The browser runs the **CORS check** on that response. Because the credentials mode is `"include"`, the check requires the field to be `true`. It is `True`. The check fails. 4. The browser converts the whole response into a **network error**: script sees a rejected call carrying a `TypeError`, with `status 0`, an empty header list and a null body. Note which side did what. The server *granted nothing* — it did not block anything. The browser *enforced*, by refusing to hand over a response it had already received in full. The body existed and was correct and was discarded. ## Why this is so hard to spot | Where you look | What you see | |---|---| | The server's access log | `200`, normal latency, correct byte count | | The server's application log | A successful, authenticated request for the tenant | | The failing call in script | A `TypeError`, no status, no headers, no body | | The response's own header block | A field that looks right to a human eye | Every signal a team normally trusts says the call succeeded. The one signal that does not is the one furthest from the server. That asymmetry — a correct-looking grant, a happy server, and an error with no status — is the signature of a value-level CORS defect rather than a routing or authentication one. ## There is no way to say "no" A natural next question is how a server expresses refusal. It does not, in the sense of a negative value: - The field defines a single value, `true`. There is no defined `false`. - Sending `Access-Control-Allow-Credentials: false` does have the effect of refusing — but only because `false` is *not* `true`, which is the same reason `True` and `1` refuse. - **Omitting the field** is the ordinary way to say that this resource is not to be read under credentials. Absence is the default. That is worth stating precisely, because "send `false` to deny" is a claim that happens to work for the wrong reason, and a candidate who explains it the wrong way has not understood the grammar. ## The generalisation worth keeping CORS grant fields are matched as literal values, not parsed loosely: - `Access-Control-Allow-Credentials` matches the byte sequence `true`, case-sensitively. - `Access-Control-Allow-Origin` is matched byte for byte against the serialized origin the browser sent, so a trailing slash or a defaulted port is a mismatch. - `null` as an origin value is the literal four-byte string, and a *missing* `Access-Control-Allow-Origin` is not the same thing as one whose value is `null`. The habit to build is to stop reading these fields as configuration written for humans and start reading them as byte sequences a state machine compares. When a CORS grant "looks right" and still fails, the comparison is where to look first. ## What to carry away - One legal value, `true`, matched case-sensitively. - Any other spelling is not a partial grant or a warning; it is no grant. - The failure surfaces as a network error with no status, while the server logs a `200`. - Refusal is expressed by omission, not by a negative value.

  • How does a server say that a resource must not be read under credentials?
    By omitting `Access-Control-Allow-Credentials` entirely. The field defines one value, `true`, and no negative form, so absence is the refusal. Sending `false` also refuses, but only because `false` is not the byte sequence `true` — the same reason `True` refuses — so it is not a separate mechanism and should not be taught as one.
  • Why does the failing call give script no status code to inspect?
    Because a failed CORS check produces a network error, not an HTTP error. The browser discards the response it received and substitutes an error response: type `"error"`, `status 0`, an empty header list and a null body. There is no status to read because the status the server sent has been thrown away before script ever sees the result.

saying these in an interview costs you the question

  • HTTP header values are case-insensitive, so True is fine
  • Sending false is the defined way to deny credentials
  • A wrong value gives a partial grant or a console warning only
  • The server must have returned an error, since the call failed
  • Any truthy value such as 1 or yes satisfies the field