skip to content

Your picking API answers its OPTIONS preflight with 401 - why can that cross-origin call never succeed?

level: middleimportance: must knowfreq 57%

answer

  1. two conditions, not one
  2. who could possibly answer that challenge?
  3. the preflight travels without any credential
  4. 401 is outside the ok range
  5. the grant must precede authentication

basics

~20 s

For two independent reasons. The preflight is sent in credentials mode "same-origin", so cross-origin it carries no cookie and no authentication entry and cannot meet the WWW-Authenticate challenge; and 401 is not an ok status, so the preflight fails regardless.

solid answer

~40 s

A `CORS-preflight request` is an `OPTIONS` request the browser constructs itself, with no body, in credentials mode `"same-origin"` - so against a cross-origin URL it carries **no cookie, no client certificate and no HTTP authentication entry**, and it does not carry the real call's `Authorization` header either; it only announces that name in `Access-Control-Request-Headers`. A `WWW-Authenticate` challenge on that exchange therefore has nobody who can answer it, and the browser never re-sends the preflight with credentials. Independently, the preflight's answer is judged on **two conditions at once**: it must have an **ok status** (200-299) **and** pass the `CORS check`. `401` fails the first outright, so even a perfect grant on it changes nothing. An API whose authenticating layer runs before anything can answer the preflight will fail here forever.

code

http · 9 lines
http
OPTIONS /picks/4821/confirm HTTP/1.1
Host: api.example.net
Origin: https://console.example.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="picking"
Content-Length: 0

go deeper

for a junior

Remember that a preflight is an anonymous OPTIONS request the browser sends first, and that if it does not come back successful the real request is never sent at all.

for a middle

Give both halves: the preflight carries no credentials so the challenge cannot be met, and 401 is outside the 200-299 ok range so the answer fails on status alone.

for a senior

Show how you would spot it in production - a run of 401 lines against OPTIONS in the access log, appearing the night an authenticating layer moved in front of the API, with no change on the calling side.

for a principal

The durable point is ordering: something must answer an anonymous, bodiless OPTIONS before credentials are demanded, and that constraint should be a known property of every service that is called cross-origin.

## The exchange that never completes Before certain cross-origin calls, a browser sends a `CORS-preflight request`: an `OPTIONS` request it constructs itself, addressed to the same URL the real call will use, carrying `Origin`, `Access-Control-Request-Method`, optionally `Access-Control-Request-Headers`, and **no body**. Its whole purpose is to ask, before anything is sent for real, whether that call may be made and whether its answer may be read. If that exchange does not complete successfully, **the real request is never sent**. There is no fallback path, no retry with different conditions, and nothing the calling code can do about it. So a preflight that can never succeed is a total outage of that call, however healthy everything else looks. ## Why the challenge cannot be answered The browser builds the preflight in credentials mode `"same-origin"`. Against a cross-origin URL that resolves to the same thing as omitting credentials: the preflight travels with - **no cookie**, whatever the site has set; - **no TLS client certificate**; - **no HTTP authentication entry** from the browser's authentication cache; - and **not** the `Authorization` header the real call intends to send - that name is merely *announced* in `Access-Control-Request-Headers`, so the server can say in advance whether it will be allowed. When an authenticating layer answers that request with `401` and a `WWW-Authenticate` challenge, it is challenging a request that has no identity and, by construction, can never acquire one. A browser does not treat that challenge as an invitation: it does not prompt, it does not attach the credential it was holding, and it does not re-issue the preflight. The exchange simply ends. ## Why 401 fails even if credentials had somehow arrived The second reason is independent of the first, and it is the one that makes "never" the right word. The preflight's answer is judged on **two conditions at once**: 1. the response must have an **ok status** - a status in the range **200 to 299** inclusive; 2. the `CORS check` on that response must pass - it must carry `Access-Control-Allow-Origin` naming the origin that asked. `401` fails the first condition on its own. A response that carries a flawless grant *and* a `401` status still ends in a network error, because the two conditions are combined, not alternatives. The same reasoning covers `403` from an authorization layer, and anything else outside the 200-299 band: the mechanism is the status range, not the number `401` specifically. `200` and `204` are the ordinary successful answers. | answer to the preflight | ok status? | grant present? | preflight result | |---|---|---|---| | `401` with `WWW-Authenticate`, no grant | no | no | fails | | `401` with a correct grant on it | no | yes | fails | | `200` with no grant | yes | no | fails | | `204` with a correct grant | yes | yes | succeeds | ## Why this appears overnight and looks like nothing This is one of the classic "it worked yesterday" failures, because nothing in the calling code changed. A warehouse wall console that has been confirming picks for a year stops at the moment an authenticating layer is placed in front of the API and begins demanding credentials of **every** request, `OPTIONS` included. In the server's own log the evidence is a run of `401` lines against `OPTIONS` - which reads exactly like ordinary unauthenticated background noise, and is therefore skipped over. The protocol requirement that falls out of this is blunt: **whatever produces the CORS answer must be able to answer the preflight before authentication is demanded**. That is a statement about ordering on the server, not about any particular product's configuration - an anonymous, bodiless `OPTIONS` has to be able to reach something that answers it with an ok status and a grant. ## Reading the failure from the calling side From script, none of this is visible. The call rejects with a bare `TypeError`; there is no status on it and no way to tell a failed preflight from a failed main request. That is why this leaf's discipline is to reconstruct the `OPTIONS` yourself and look at what comes back, rather than to infer anything from the rejection. One more thing follows from the credentials rule. Because the preflight is anonymous, the server cannot make its grant depend on who is calling: at the moment it must answer, it does not know. The grant is a statement about an **origin**, not about a user.

  • Does the same reasoning apply to a preflight answered 403 by an authorization layer?
    Yes. The condition is an ok status, meaning 200 to 299 inclusive, so `403` ends the preflight just as `401` does - and for the simpler of the two reasons, since no challenge is even involved. Any status outside that band fails, and the real request is never sent.
  • If the preflight carries no credentials, how does the API ever see the console's Authorization header?
    On the actual request, which is sent only once the preflight has succeeded. The preflight merely announces the header's name in `Access-Control-Request-Headers` so the server can grant it in advance; the header itself travels on the second exchange, which is authenticated normally.
  • Can the server decide the grant based on which user is calling?
    Not at preflight time. The preflight is anonymous by construction, so when the server must answer it there is no user to consider. A grant is a statement about a calling origin, and any per-user decision has to happen on the real request instead.

saying these in an interview costs you the question

  • Thinks signing in first makes the preflight authenticate.
  • Believes a correct grant header rescues a 401 answer.
  • Says the browser retries the preflight with credentials attached.
  • Expects the Authorization header to travel on the preflight itself.
  • Dismisses 401 lines on OPTIONS requests as ordinary noise.