A CORS error is usually a mismatch between the browser’s request and the API’s response headers. This guide provides a repeatable workflow for reading the console message, inspecting preflight requests, checking credentials, and correcting server or proxy configuration without weakening security.
Overview
Cross-Origin Resource Sharing, or CORS, is a browser-controlled mechanism for deciding whether JavaScript running on one origin may read a response from another origin. An origin is the combination of scheme, host, and port. For example, https://app.example.test and https://api.example.test have different origins even though they share a parent domain.
The browser adds an Origin request header when a cross-origin request requires CORS handling. The server must then return a compatible Access-Control-Allow-Origin response header. If the header is missing or does not match, the request may reach the server, but browser JavaScript is prevented from reading the response. This is why a request can appear successful in server logs while the frontend reports a CORS error.
CORS is not an authentication system and does not stop a client such as curl, a command-line API client, or another server from sending a request. It controls browser access to responses. Authentication, authorization, CSRF defenses, rate limits, and input validation still belong in the application and API design.
Start with the browser’s Network panel rather than changing headers at random. Record the frontend origin, API URL, HTTP method, request headers, response status, and whether the browser sent an OPTIONS request. That information usually identifies the failing layer.
Checklist by scenario
1. The console says the response is missing Access-Control-Allow-Origin
- Copy the exact frontend origin from the console or request details, including scheme and port.
- Confirm that the API response includes
Access-Control-Allow-Origin. - Compare the value with the requesting origin. A trailing slash, different port, or HTTP-versus-HTTPS difference can matter.
- Check every response path, including errors, redirects, authentication failures, and framework-generated exceptions.
- Restart or redeploy the API after changing configuration, then clear any relevant proxy or application cache.
For a public endpoint that does not use browser credentials, a server may intentionally return Access-Control-Allow-Origin: *. If the application uses cookies or credentialed requests, use an explicit allowed origin instead; a wildcard cannot be combined with credentialed CORS access.
2. An OPTIONS preflight request fails
Browsers commonly send a CORS preflight before a request that is not a “simple” cross-origin request. A preflight uses the OPTIONS method and may include:
Origin: the frontend origin making the request.Access-Control-Request-Method: the intended method, such asPATCH.Access-Control-Request-Headers: headers the actual request plans to send, such asAuthorizationorContent-Type.
Inspect the OPTIONS response and confirm that it has an appropriate status, does not require normal application authentication unexpectedly, and includes compatible CORS headers. Depending on the request, the server may need to return:
Access-Control-Allow-Origin: https://app.example.test
Access-Control-Allow-Methods: GET, POST, PATCH, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
A proxy, load balancer, web server, or route handler may be returning a 404, 405, 301, 302, or authentication response to OPTIONS before the application’s CORS middleware runs. Fix that routing path rather than only changing the frontend request.
3. The request works without cookies but fails with credentials
Credentialed requests include browser-managed credentials such as cookies. On the frontend, this may be enabled with credentials: "include" in fetch or an equivalent HTTP-client setting. The server must explicitly allow credentials with:
Access-Control-Allow-Credentials: true
It must also return a specific origin in Access-Control-Allow-Origin, not *. Check cookie attributes and deployment settings separately: a cookie may still be excluded because of its domain, path, Secure, or SameSite behavior. CORS headers alone do not make a cookie eligible for cross-site use.
4. The API works in an API client but not in the browser
This difference is expected when the server handles the request but does not grant browser JavaScript permission to read it. Compare the browser request with the successful API-client request. Look for an added Origin header, a browser preflight, different authorization handling, redirects, or a missing response header on an error status.
Use an API testing tool to verify the endpoint’s application behavior, but use browser developer tools to diagnose CORS itself. For a broader tooling workflow, see the API testing tools comparison.
5. The request fails only after deployment
List every origin involved: the production frontend, API, authentication service, CDN, preview deployment, and local development server. A setting that allows http://localhost:3000 does not automatically allow a production HTTPS origin. Check environment variables, reverse-proxy rules, HTTPS redirects, and whether the deployed service is actually receiving the request.
What to double-check
| Header | Purpose | Common check |
|---|---|---|
Access-Control-Allow-Origin | Identifies an origin allowed to read the response. | Match the request’s Origin; do not add a path to the origin value. |
Access-Control-Allow-Methods | Lists methods permitted for the cross-origin operation. | Include the actual method when a preflight requests it. |
Access-Control-Allow-Headers | Lists non-safelisted request headers the browser may send. | Include headers such as Authorization or the required content type. |
Access-Control-Allow-Credentials | Allows credentialed browser requests when set to true. | Use an explicit origin; do not combine it with a wildcard origin. |
Access-Control-Expose-Headers | Allows frontend code to read selected response headers that are not exposed by default. | Add only the response headers the application needs. |
Access-Control-Max-Age | Lets the browser cache a successful preflight for a period. | Use it carefully while configuration is changing; cached preflight results can confuse testing. |
Vary: Origin | Helps shared caches distinguish responses generated for different origins. | Consider it when the server reflects an allowed origin dynamically. |
Use the Network panel to inspect both the preflight and the actual request. Do not rely only on the console summary: it may hide the response status, redirect chain, or header that caused the failure. Also check whether a service worker, CDN, or reverse proxy is serving an older response.
When configuring an allowlist, prefer exact known origins and keep development, preview, and production values separate. If the allowed origin is selected dynamically, validate it against a controlled list rather than reflecting every value supplied by the browser.
Common mistakes
- Adding CORS headers to the frontend. The API or response-producing server must grant access. A frontend header cannot authorize itself.
- Allowing only the happy path. Add consistent CORS handling to successful responses and relevant error responses, while still avoiding disclosure of sensitive error details.
- Forgetting custom headers. An
Authorizationheader or a non-safelisted content type can trigger preflight. - Handling only the application route. OPTIONS may be intercepted by a web server, router, gateway, or authentication layer before the application code runs.
- Using a wildcard with cookies. Credentialed requests need an explicit allowed origin and compatible cookie settings.
- Disabling browser security locally. Launching a browser with security checks disabled can conceal the real configuration problem and is unsafe for normal browsing.
- Assuming CORS fixes authorization. CORS controls browser response access; it does not replace authentication or permission checks.
- Changing several layers at once. Record the original request and change one configuration point at a time so the successful fix is identifiable.
For local testing, use separate development origins and a deliberate allowlist. A small local proxy can be useful when its purpose is understood, but it should not be treated as proof that production CORS is correct. Avoid pasting real tokens, private customer data, or sensitive API responses into public browser-based utilities; the same handling discipline applies when using developer tools such as a JSON formatter and validator.
When to revisit
Return to this checklist whenever the frontend domain, API host, port, protocol, authentication method, proxy, CDN, or framework middleware changes. Recheck it before a production deployment that introduces a preview origin, moves from HTTP to HTTPS, adds cookies, or adds a custom request header. It is also worth revisiting after a browser, reverse proxy, API gateway, or server framework upgrade changes request routing or header handling.
A practical maintenance routine is to keep one documented test for each supported frontend origin and request type: a simple read, a preflighted write, and a credentialed request if the application uses cookies. Run those checks after environment changes and verify the OPTIONS response separately. Keep the expected origins and methods in deployment configuration rather than scattering them across route handlers.
When an error returns, work in this order: capture the exact origin, inspect the Network panel, identify whether OPTIONS was sent, compare requested and allowed methods and headers, verify credentials rules, then check proxies and error responses. This sequence turns a vague “CORS policy” message into a specific header, route, or environment mismatch that can be corrected safely.