Why Does a POST Request Trigger Two Calls? Understanding CORS Preflight and Same‑Origin Policy
The article explains why a POST request can result in two HTTP calls by detailing the browser's same‑origin policy, the role of CORS, the conditions for simple requests, and how preflight OPTIONS requests are automatically issued to verify cross‑origin permissions.
Same‑Origin Policy
The same‑origin policy restricts a document or script loaded from one origin (protocol, host, and port) from interacting with resources from a different origin. Two URLs are considered same‑origin only when their protocol, host, and port are identical. http://test.home.com:8080/dir/page.html – Same origin (only the path differs) http://test.home.com:8080/dir/inner/another.html – Same origin (only the path differs) https://test.home.com:8080/secure.html – Different origin (protocol differs) http://test.home.com:8081/dir/etc.html – Different origin (port differs) http://online.home.com:8080/dir/other.html – Different origin (host differs)
The policy impacts three aspects of a web page:
DOM access – scripts cannot read or manipulate the DOM of a document from another origin.
Web data – APIs such as XMLHttpRequest or fetch can only request resources from the same origin.
Network communication – browsers block responses from cross‑origin requests unless the server explicitly permits them via CORS.
Cross‑Origin Resource Sharing (CORS)
CORS provides a controlled mechanism for browsers to request resources from a different origin. The server must include specific HTTP response headers that list the allowed origins, methods, and request headers.
Simple requests
A request is classified as *simple* and does not trigger a preflight request when all of the following conditions are met:
HTTP method is GET, HEAD or POST.
Allowed request headers are limited to the following set:
Accept Accept-Language Content-Language Last-Event-ID Content-Typewith a value of application/x-www-form-urlencoded, multipart/form-data or text/plain Browser‑specific headers such as DPR, Save-Data, Viewport-Width, Width No ReadableStream objects are used in the request body.
No custom request headers are present.
No event listeners are attached to XMLHttpRequestUpload objects.
Preflight requests
When a request does not satisfy the simple‑request criteria, the browser first sends an OPTIONS request—called a *preflight request*—to the target server. The preflight request includes two special request headers: Access-Control-Request-Method – the HTTP method that the actual request will use (e.g., POST). Access-Control-Request-Headers – a comma‑separated list of custom headers that the actual request intends to send (e.g., content-type,x-secsdk-csrf-token).
If the server approves the preflight request, it responds with the corresponding Access-Control-Allow-* headers, for example: Access-Control-Allow-Origin – the origin that is permitted (or a specific origin when credentials are involved). Access-Control-Allow-Methods – the list of HTTP methods allowed. Access-Control-Allow-Headers – the list of request headers allowed. Access-Control-Max-Age – optional, specifies how long (in seconds) the preflight response can be cached (e.g., 86400 seconds = 1 day).
After a successful preflight, subsequent actual CORS requests include an Origin header, and the server’s response includes Access-Control-Allow-Origin. The browser then forwards the response to the script.
Credentialed requests and wildcard restrictions
When a request includes credentials such as cookies, the server must not use a wildcard ( *) for Access-Control-Allow-Origin. Instead, it must echo the exact requesting origin (e.g., https://example.com) to prevent credential leakage. Likewise, Access-Control-Allow-Headers and Access-Control-Allow-Methods should list explicit values rather than *, because unrestricted headers or methods could be abused.
Full request flow
The diagram below shows the complete sequence from the browser’s preflight request to the final CORS request and response.
Conclusion
A preflight request is an automatic OPTIONS request sent by the browser when a cross‑origin request (such as a POST) does not qualify as a simple request. Its purpose is to let the server decide whether the actual request is allowed, thereby enforcing the same‑origin policy and protecting user data from unintended cross‑origin access.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
