On this page
A request can work from a terminal and fail from a browser because CORS is a browser-enforced sharing policy. The server may be reachable and return a valid response, yet browser JavaScript is not allowed to read it. Fixing the problem starts with identifying the exact origin and request shape, not adding permissive headers everywhere.
Reproduce the browser's actual request
An origin is a scheme, hostname, and port combination. A different subdomain or development port is a different origin even when both applications belong to the same company. Record the frontend origin, destination URL, method, request headers, cookie usage, and any redirects.
Use the API Latency Tester for a basic reachability and status check when its execution environment permits it. If a browser tool encounters CORS itself, that is a limitation of the test path rather than proof the API is down. The browser's Network panel remains the best place to inspect the exact preflight and actual request pair.
Review the MDN CORS guide for the distinction between simple requests and preflighted requests. A JSON POST commonly triggers a preflight because application/json is not a safelisted Content-Type. An Authorization header also commonly causes preflight.
Understand the OPTIONS negotiation
Before sending certain cross-origin requests, the browser sends OPTIONS with an Origin header and headers describing the proposed method and non-safelisted headers. The server responds with the allowed policy. The browser evaluates that response before deciding whether to send the actual request.
OPTIONS /v1/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Vary: OriginThese are illustrative request and response messages, not one message to paste into server code. Configure the policy using maintained middleware or gateway facilities. Header names are case-insensitive, but header values such as the allowed origin must follow the protocol's matching rules.
Do not require a logged-in cookie on the preflight itself. Cross-origin preflight requests generally exclude credentials, so ordinary session authentication can reject them before CORS policy is evaluated. Allowing OPTIONS to describe policy does not mean the subsequent POST should bypass authentication or permission checks.
Configure explicit origins and credentials
For an authenticated browser application, allow a deliberate set of trusted frontend origins. Parse and compare complete origins rather than using substring checks such as whether the value contains your company name. An attacker-controlled hostname can contain the same text without belonging to your organization.
A credentialed response cannot use Access-Control-Allow-Origin: *. Return the specific allowed origin and, when needed, Access-Control-Allow-Credentials: true. The browser client must also request the appropriate credentials behavior. Cookie SameSite attributes and browser third-party-cookie policies can still prevent cookies from being sent; CORS headers do not override those rules.
If responses vary by the request's Origin header, send Vary: Origin so shared caches do not reuse one origin's policy incorrectly for another. When you dynamically vary preflight responses by requested method or headers, account for those dimensions in caching as well.
Never reflect every supplied Origin while also allowing credentials. That effectively authorizes arbitrary websites to read protected responses in eligible browser contexts. For a truly public unauthenticated resource, a wildcard can be appropriate, but it should be a conscious resource policy rather than a universal debugging workaround.
Apply policy to errors and infrastructure layers
A common failure is correct CORS on successful routes but missing headers on 401, 403, 404, or 500 responses. The browser then reports a CORS failure while hiding the useful application error. Place policy handling where it covers the relevant error paths without granting unauthorized access.
Check gateways, reverse proxies, redirects, and framework middleware order. An authentication service may redirect an API request to an HTML login page on another origin. Prefer a clear API authentication response where appropriate. A gateway may also reject OPTIONS or strip headers before the request reaches the application.
Avoid duplicate policy headers from multiple layers. Two Access-Control-Allow-Origin values do not create a valid list of authorized origins. Choose one layer as the policy owner and test the full deployed path, not only a local framework instance.
To let JavaScript read non-safelisted response headers, configure Access-Control-Expose-Headers for the specific fields it needs, such as a documented request identifier. This is different from Access-Control-Allow-Headers, which concerns request headers. MDN's expose-headers reference explains the distinction.
Test negative cases and avoid false security claims
CORS is not authorization and does not prevent a non-browser client from sending requests. It also does not universally prevent a browser from sending a state-changing request; some requests can be sent even when the response cannot be read. Cookie-authenticated APIs still need an appropriate CSRF strategy.
Do not use mode: 'no-cors' as a fix for an API call that needs readable JSON. It produces an opaque response and restricts request behavior rather than granting permission. The Fetch standard defines the underlying model; consult WHATWG Fetch when troubleshooting edge cases.
Build tests for an allowed origin, a disallowed origin, no Origin header, unauthorized actual requests, custom headers, and credentialed calls. Verify both preflight and actual responses through the deployed gateway. Use a short preflight cache lifetime during rollout, then increase it deliberately once policy is stable. Browser caps and cache behavior vary.
Inspect synthetic error payloads with the JSON Formatter, and document supported origins and local-development ports in your backend integration reference. A useful support report includes the exact origin, requested method and headers, status of OPTIONS, and whether the actual request was ever sent. That evidence is far more actionable than a screenshot saying only that CORS failed.
Reproduce a preflight without confusing it with enforcement
When the browser reports a failed preflight, reproduce the OPTIONS request with the exact Origin, Access-Control-Request-Method, and Access-Control-Request-Headers values from DevTools. Compare the response with the browser trace. A successful command-line request proves that the server answered; it does not prove the browser will allow JavaScript to read the subsequent response, because command-line clients do not enforce CORS.
Next, send the actual method separately with the same origin and expected authentication. An OPTIONS response can be correct while the actual endpoint omits its access-control headers or fails authorization. Keep those two outcomes distinct in your notes.
After changing policy, account for cached preflight results. Use a fresh browser context or otherwise verify a new OPTIONS exchange before concluding the configuration has no effect. Check intermediaries too: an origin-varying response served from a shared cache without the proper variation can make the problem appear intermittent across otherwise identical clients.
Key takeaways
- Inspect preflight and actual requests separately before changing server policy.
- Use exact trusted origins and deliberate credential settings.
- Apply CORS consistently across success, error, gateway, and cache paths.
- Keep authentication, authorization, and CSRF protection independent of CORS.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor