Launch offer: the first 1,000 users get Settl free for a year*Claim your spot
settlbuilding in public

Debug CORS With the Actual Browser Preflight

When a browser blocks your frontend from calling your own API, the visible request is often not the request that failed. The browser first sent an OPTIONS preflight asking whether this origin, method, headers, and credential mode were allowed. Your API gave the wrong answer, so the browser never released the real response to the page.

This pack starts with the network evidence and produces the smallest server-side allowlist change that fixes the intended origin without opening the API to every tab on the internet.

Reproduce the preflight exactly

Copy these values from the browser network panel:

Then replay the preflight:

curl -i -X OPTIONS https://api.example.com/orders \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type,x-request-id'

Check the response, not just the status. It needs the intended Access-Control-Allow-Origin, method, and headers. When credentials are enabled, the allowed origin must be explicit. * and credentialed browser requests do not belong together.

The common breakpoints

  1. Auth middleware rejects OPTIONS before CORS headers are added.
  2. The server allows http://localhost:3000 but the real page uses HTTPS or another port.
  3. A proxy strips the headers emitted by the application.
  4. The preflight allows GET but the page sends POST or PATCH.
  5. The page includes a custom header that is missing from Access-Control-Allow-Headers.
  6. The server reflects any Origin value while also allowing credentials.

CORS is a browser boundary. A successful server-to-server curl request without an Origin header proves the endpoint works, not that the browser is allowed to expose it to that page.

Use an allowlist, not a mirror

Keep trusted origins in configuration and compare the full origin string. Add the response header only when the incoming origin is on that list. Send Vary: Origin when a cache may store different responses per origin.

Do not solve this by turning off browser security, installing a permissive extension, or returning the incoming origin blindly. Those approaches remove the control you were trying to configure.

What the AI does and does not decide

Claude can compare the browser request with server and proxy configuration, point to the first mismatch, and draft a patch. Code must enforce the allowlist. A person should approve any change that expands which websites can make credentialed requests.

The pack keeps both a positive and negative browser test in the plan: the trusted frontend should work, and an unlisted origin should still fail.

Run the starter locally

npm test
npm run validate
npm run sample

Docker Compose runs the Node workflow boundary and n8n. Dry-run validates the evidence and returns a proposed diagnosis without changing a server or proxy.

Download the runnable pack

Get the next one in your inbox