1. Find the OPTIONS request
Open DevTools, select Network, and filter for OPTIONS. Inspect the preflight request separately from the intended GET, POST, PUT, PATCH, or DELETE request. The important request headers are:
Origin: the exact scheme, host, and port making the request.Access-Control-Request-Method: the method the browser wants to send.Access-Control-Request-Headers: the non-safelisted headers the browser wants to include.
A JSON Content-Type, an Authorization header, or methods such as PUT and DELETE commonly trigger a preflight. The preflight itself is expected browser behavior, not the error.
2. Reproduce the browser's preflight
Use curl with the same origin, method, and header names shown in DevTools:
curl -i -X OPTIONS 'https://api.example.com/widgets' \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type,authorization'Curl does not enforce CORS, which makes it useful for viewing the server's raw response. It does not prove the browser will accept that response; compare each returned header with the browser's request.
3. Validate every allow header
A successful response can use any successful status; 204 No Content is a common choice:
HTTP/2 204
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Vary: Origin| Check | Failure to look for |
|---|---|
| HTTP status | The route returns a redirect, 401, 403, 404, or 500 instead of a successful OPTIONS response. |
| Allowed origin | The value is missing, has a trailing-slash mismatch, or uses * with credentials. |
| Allowed method | The requested method is absent from Access-Control-Allow-Methods. |
| Allowed headers | A requested header such as Authorization is absent from Access-Control-Allow-Headers. |
| Credentials | The request uses credentials but Access-Control-Allow-Credentials: true is absent. |
Header names are case-insensitive, but values still need to describe what the browser requested. When origin handling is dynamic, return the validated origin and add Vary: Origin so shared caches do not reuse a response for the wrong site.
4. Check routers, authentication, redirects, and proxies
OPTIONS must reach the CORS handler. A common failure is middleware that demands authentication before the CORS layer runs. Preflights normally do not contain the credentials for the actual request, so let OPTIONS reach the policy handler and return its permission response.
- Define an OPTIONS route for the same URL as the actual API endpoint.
- Check whether an HTTP-to-HTTPS or trailing-slash redirect intercepts the route.
- Verify that a CDN, load balancer, WAF, or reverse proxy forwards OPTIONS and preserves CORS headers.
- Inspect error responses too. A proxy-generated 403 or 500 without CORS headers appears to the browser as a CORS failure.
5. Verify the actual response after preflight succeeds
A successful preflight only permits the browser to send the next request. The actual response must still return a matching Access-Control-Allow-Origin header and, when credentials are used,Access-Control-Allow-Credentials: true. Inspect both Network entries before declaring the issue fixed.
Paste the request details into the CORS Debugger for a policy check. For broader issues, including simple requests and framework examples, continue with thecomplete CORS debugging guide.
Fast diagnosis checklist
- Find the OPTIONS request: Use the browser Network panel to inspect the preflight separately from the actual request.
- Reproduce the preflight: Send an OPTIONS request with the same Origin, requested method, and requested headers.
- Validate the allow response: Check that the response explicitly permits the origin, method, headers, and credentials in use.
- Trace the full request path: Verify that authentication, redirects, routers, proxies, and CDNs do not intercept OPTIONS.
- Verify the actual response: After preflight succeeds, confirm the real response also contains the required CORS headers.