Why can an OPTIONS preflight answered with a 3xx redirect never authorise the request that would follow it?
answer
- judged where it stands
- no second hop for a preflight
- a redirect status is not ok
- canonicalisation broke it overnight
- the called URL must answer itself
basics
~20 sBecause a preflight's own answer is judged where it stands: the browser does not chase the redirect and judge the second response instead, and a redirect status is not an ok status, so the preflight fails and the real request is never sent.
solid answer
~40 sThe answer to a `CORS-preflight request` is judged **directly**. The browser does not follow the redirect, re-issue the `OPTIONS` at the target, and then judge that answer - it evaluates the response it was given, against an **ok status** (200-299) and a passing `CORS check`. A redirect status is not an ok status, so the preflight fails there and the real request is never sent. What makes this a live production failure is where redirects come from: a canonicalising rule added in front of the API - a trailing slash, a host prefix, a scheme upgrade, a path that moved - turns every preflight into a `3xx`. The server's log then shows nothing but healthy redirects while the calling page is completely dead.
code
http · 8 linesOPTIONS /picks/4821/confirm HTTP/1.1
Host: api.example.net
Origin: https://console.example.net
Access-Control-Request-Method: POST
HTTP/1.1 301 Moved Permanently
Location: https://edge.example.net/picks/4821/confirm
Content-Length: 0go deeper
Remember the rule rather than the mechanism: an OPTIONS preflight that is answered with a redirect has failed, and the call it was asking about will never be sent.
Explain that the preflight's own response is judged in place against an ok status, so a redirect is not followed and not re-judged at its target.
Recognise the production signature: a call that breaks on a night when no application code shipped, and an access log full of healthy redirects on OPTIONS lines after a canonicalisation rule was added.
Treat it as a change-management property: URL-shape rules applied estate-wide are invisible to same-origin traffic and fatal to cross-origin callers, so they need a review path that knows which services are called from another origin.
## What a preflight's answer is judged on A `CORS-preflight request` is an `OPTIONS` request the browser sends to the exact URL the real call will use, carrying `Origin`, `Access-Control-Request-Method` and, where relevant, `Access-Control-Request-Headers`, with no body. Its answer is judged on two conditions together: 1. the response must carry an **ok status** - 200 to 299 inclusive; 2. the `CORS check` must pass on it - `Access-Control-Allow-Origin` must name the origin that asked. A redirect status fails the first condition. There is no third branch in which a redirect is "neither pass nor fail" and gets resolved first. ## Why the redirect is not chased The intuition people bring to this is the ordinary one for a browser: a redirect means *go there instead*, and the exchange continues at the new URL. The preflight does not work that way. **Its own response is the one under judgement.** The browser does not re-send the `OPTIONS` to the target named in `Location`, does not judge what that target would have said, and does not send the real request to the redirect target either. The preflight ends at the redirect, the call ends with the preflight, and the calling script gets a bare `TypeError`. The consequence to state plainly: **a redirect answer to a preflight is a permanent failure of that call**, not a detour. ## Where the redirect comes from in practice Nobody configures a redirect on a preflight deliberately. It arrives as a side effect of a change made for a different reason, and the usual sources are all canonicalisation: - **a trailing-slash rule**, which redirects `/picks/4821/confirm` to the same path with or without a final slash; - **a host canonicalisation**, redirecting a bare host to a prefixed one or to a newly introduced edge host; - **a scheme upgrade**, redirecting plain-HTTP calls to the secure scheme; - **a path that moved**, with a redirect left behind so existing callers keep working; - **a case- or encoding-normalising rule** applied to every path that reaches the API. Every one of these is a well-intentioned, invisible change, and every one of them is fatal to a preflighted cross-origin call - while a same-origin caller through the same rule notices nothing at all, because its requests are simply followed to the canonical URL as usual. That difference is what makes the change look innocent when it ships. ## Why the server's log looks clean This is the second half of the leaf's lesson about evidence. In the access log, the failing exchanges appear as: ```http OPTIONS /picks/4821/confirm HTTP/1.1 HTTP/1.1 301 Moved Permanently Location: https://edge.example.net/picks/4821/confirm ``` A `301` is a perfectly ordinary, successful thing for a server to do. Nothing is logged as an error, no handler threw, no status in the 400 or 500 bands appears anywhere, and the count of requests may not even drop, because the browser keeps issuing preflights. The entire failure lives in the browser's judgement of those healthy-looking lines, and the calling side sees only an error object with no status and no body. ## How you confirm it and what fixes it Confirming it takes one exchange: re-issue the `OPTIONS` to the URL the page actually calls, with the `Origin` the page actually sends, and look at the status line that comes back. If it is a redirect, you are finished - the grant on the target is irrelevant, because nothing will ever read it. The fix is to remove the redirect from the path the page takes, which in practice means one of two things: 1. **the page calls the final URL directly**, so the preflight is answered at the URL it was sent to; or 2. **the URL the page calls stops redirecting** - the canonicalising rule is scoped so that this path answers in place. Either way the requirement is the same and worth memorising as a rule: **the URL a cross-origin page calls must answer its preflight itself.** Anything that stands between the call and its answer and says "go somewhere else" ends the call. A useful check to carry out of this: if a cross-origin call breaks on a night when no application code shipped, look for something that changed the shape of URLs rather than something that changed the API.
- The API log shows only 301 lines for those OPTIONS requests and no errors at all - how is that consistent with a total outage of the console?A `301` is a normal, successful server outcome, so nothing in the log reads as a fault. The failure happens entirely in the browser's judgement of that answer: the status is outside the ok range, the preflight fails, and the real request is never sent. The log is showing you the cause, styled as health.
- Does re-pointing the page at the redirect target fix it?It does, provided that target answers the preflight itself with an ok status and a grant naming the calling origin. The requirement is not about which URL is canonical but about there being no redirect at all on the URL the page calls.
saying these in an interview costs you the question
- Assumes the browser follows the redirect and carries on.
- Thinks a 301 answer is a healthy preflight outcome.
- Expects the real request to be sent to the redirect target.
- Blames the calling code for an infrastructure-level change.
- Looks only for 4xx and 5xx lines in the access log.