What happens to a `Set-Cookie` on a cross-origin response whose CORS check then fails?
answer
- two steps, and only one is filtered
- processed while fetching, checked afterwards
- the write survives the refused read
- credentials mode gates the storing
- reload reveals the sign-in that failed
basics
~20 sIf the request's credentials mode was "include", the cookie is already stored: the browser processes Set-Cookie while fetching, before the CORS check runs. The check then refuses script the response, so the write happened and the read did not.
solid answer
~40 s`Set-Cookie` on a cross-origin response is honoured only when the request's credentials mode is `"include"` — an uncredentialed cross-origin fetch ignores it entirely. When the mode is `"include"`, the cookie is processed as part of receiving the response, which happens *before* the CORS check filters what script gets. So a response that sets a session cookie and then fails the check leaves the cookie stored while script receives a network error: a `TypeError`, `status 0`, no headers, no body. A cross-origin sign-in can therefore report failure to the page and still log the user in, which becomes visible on the next reload. Script can never read `Set-Cookie` itself, even if the server names it in `Access-Control-Expose-Headers`.
code
http · 7 linesHTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: tenant_session=8f2b9c
Access-Control-Allow-Origin: https://portal.example
Vary: Origin
{"tenant":"t-4471"}go deeper
Remember that a browser can store a cookie from a response your code was never allowed to read; the two outcomes are decided at different moments.
Explain the ordering: Set-Cookie is processed while the response is received, and the CORS check runs afterwards on what script may see.
Recognise the incident shape — a sign-in that reports failure and then works after a reload — and know it points at a grant missing on one response path.
The general lesson is that a read-side control never unwinds a side effect, which is the same property that makes cross-site writes a separate problem from cross-site reads.
## Two things happen to one response When a cross-origin response arrives, the browser does two separable things with it: 1. **Processes it as an HTTP response.** Among other things, that means acting on `Set-Cookie` — storing the cookie in the browser's cookie store. 2. **Filters it for script.** The CORS check runs, and either the response is handed to the calling code or it is replaced by a network error. Those steps happen in that order, and the second cannot undo the first. A response whose CORS check fails has already had its cookies stored. ## The credentials mode gates step one Step one is conditional. `Set-Cookie` on a **cross-origin** response is acted on only when the request's credentials mode is `"include"`: - mode `"omit"` or `"same-origin"` on a cross-origin request → no credentials sent, and `Set-Cookie` on the way back is ignored; - mode `"include"` → credentials sent, and `Set-Cookie` on the way back is honoured. So the credentialed mode is what makes the write possible in the first place. The CORS grant is not: it governs only step two. ## The confusing incident A tenant portal signs in against an authentication API on a second host. The call is credentialed. The API answers `200`, sets the session cookie, and — because the sign-in path's response-header block was never updated — omits `Access-Control-Allow-Credentials`. What the team sees: 1. The page reports a failed sign-in and shows an error, because script received a network error with no status and no body. 2. The API's log shows a successful authentication. 3. The user reloads the portal out of frustration and is **signed in**. Every one of those observations is correct, and together they look impossible until you separate the write from the read. | | Write (`Set-Cookie`) | Read (response body) | |---|---|---| | Gated by | Credentials mode `"include"` | The CORS check on the response | | Happens when | The response is received | After the check passes | | Effect of a failed check | None — already done | Replaced by a network error | | Visible to script | Never | Only when the check passes | ## Script never reads `Set-Cookie` A related and frequently attempted workaround does not work: naming `Set-Cookie` in `Access-Control-Expose-Headers` does not make it readable. The browser withholds that field from script regardless of the grant, on any response, cross-origin or not. Exposure widens which headers script may read among those it is permitted to see at all; it cannot promote a field that is never handed to script. This matters for reasoning about the incident above: there is no way for the page to observe that the cookie was set. The only evidence is the effect on the next request. ## Why the order is designed this way CORS is a rule about **reading**, applied at the boundary between the network stack and the calling script. Cookie storage sits below that boundary, in the part of the browser that handles HTTP. Making a failed check retroactively unwind cookie storage would mean pushing a script-facing permission rule down into the HTTP layer, which is not where it lives. The consequence — a side effect that survives a refused read — is the same consequence the same-origin policy has always had: it never prevented the request or its effects, only the reading of the answer. ## What to carry away - `Set-Cookie` on a cross-origin response is honoured only under credentials mode `"include"`. - The cookie is stored before the CORS check runs, so a failed check does not unset it. - Script gets a network error: `TypeError`, `status 0`, no headers, no body. - A cross-origin sign-in can report failure and still succeed; the reload reveals it. - `Set-Cookie` is never readable by script, exposed or not.
- The same response is fetched with credentials mode `"same-origin"` instead. What changes?Nothing is stored. On a cross-origin request that mode attaches no credentials and the response's `Set-Cookie` is ignored, so the cookie never enters the store whatever the grant says. The read side is unaffected by the change in isolation: script still needs a passing CORS check, but the credentials rules no longer apply to it, so the response need not carry `Access-Control-Allow-Credentials` at all.
- Can the page detect that the cookie was set, given the call reported an error?Not from the response. `Set-Cookie` is never handed to script, and naming it in `Access-Control-Expose-Headers` does not change that; a failed check leaves no headers to read in any case. The only observable evidence is behavioural — the next request carries the cookie and the server treats the user as signed in.
saying these in an interview costs you the question
- A failed CORS check discards the response, so no cookie is stored
- Naming Set-Cookie in Expose-Headers lets script read it
- Set-Cookie works cross-origin regardless of the credentials mode
- The browser rolls back side effects when it blocks a read
- Script receiving an error means the server rejected the sign-in