A CORS error means the browser refused to expose a cross-origin response to your frontend—not necessarily that the API is down. This guide explains how to diagnose CORS failures, understand preflight requests, verify response headers, and apply safe configuration patterns across common backend stacks.
Overview
Cross-Origin Resource Sharing, or CORS, is a browser-controlled mechanism for making requests between different origins. An origin is defined by its scheme, host, and port. For example, https://app.example.com and https://api.example.com have different origins even though they share a parent domain. Likewise, http://localhost:3000 and http://localhost:8000 are different origins during local development.
When JavaScript sends a request to another origin, the browser checks whether the server permits the frontend origin. The server communicates that decision with response headers such as:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
The browser may send the request directly, or it may first send an OPTIONS preflight request. A preflight is commonly triggered by methods such as PUT, PATCH, or DELETE, or by headers such as Authorization and certain non-simple content types. The preflight asks whether the actual request is allowed. If the response is missing, blocked, redirected, or incorrectly configured, the browser stops before sending the real request.
CORS is enforced by browsers. A command-line client, server-side application, or API testing tool may receive the same response without reporting a CORS error. That difference is useful during debugging: a successful request in an API client does not prove that browser access is configured correctly.
Checklist by scenario
Scenario 1: A basic GET request is blocked
- Copy the exact frontend origin from the browser address bar, including scheme and port.
- Check whether the API response contains
Access-Control-Allow-Origin. - Compare its value with the requesting origin. An origin mismatch includes small differences such as
httpversushttps, a different port, or an unexpected trailing configuration value. - Inspect the Network panel rather than relying only on the console message. Look at the request URL, status code, response headers, and whether the response was a redirect or an error page.
- Confirm that the header is added to error responses as well as successful responses. A server may allow a normal response but omit CORS headers when authentication fails or an exception occurs.
Scenario 2: An OPTIONS preflight fails
Find the OPTIONS request in the browser’s Network panel. The response should be produced by the API or its gateway, not rejected by a web server before the application handles it. Check the following items:
Access-Control-Allow-Originmatches the requesting origin.Access-Control-Allow-Methodsincludes the method used by the actual request.Access-Control-Allow-Headersincludes headers requested by the browser, such asAuthorizationorContent-Type.- The OPTIONS route is not requiring application authentication or a request body.
- The proxy, load balancer, web server, and API application agree on how OPTIONS requests are handled.
The browser’s preflight request usually includes headers such as Origin, Access-Control-Request-Method, and Access-Control-Request-Headers. Use those values as your checklist rather than guessing which permissions are needed.
Scenario 3: Requests include cookies or credentials
For cookie-based sessions or other browser credentials, the frontend must explicitly request credentials and the server must explicitly allow them. The response commonly needs:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
When credentials are included, do not use a wildcard origin as a substitute for a specific allowed origin. Configure the trusted frontend origins deliberately, and verify cookie attributes, domain, path, and secure transport separately. A CORS header cannot make an incorrectly scoped cookie available.
Scenario 4: Local development works, but deployment fails
Compare the deployed frontend origin with the origin allowed by the deployed API. Local settings often permit a development port, while the production frontend uses a different hostname or HTTPS scheme. Also check environment variables, reverse-proxy configuration, and whether the production request is reaching the expected API rather than a fallback HTML page.
For local setup details, the local development environment guide can help separate application bugs from environment and routing problems.
Scenario 5: A framework configuration appears correct
Framework middleware is only one part of the request path. A typical configuration pattern is to register CORS middleware early, define allowed origins from environment-specific configuration, and ensure that authentication, routing, and proxy layers do not intercept OPTIONS requests first.
In an Express-style Node.js application, the configuration concept looks like this:
app.use(cors({
origin: ["https://app.example.com"],
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true
}));
In Python frameworks such as FastAPI or Flask, the names differ, but the decisions are the same: allowed origins, methods, headers, and whether credentials are permitted. Apply the settings at the application or gateway layer that actually returns the response, then verify the result in the browser.
What to double-check
| Browser symptom | Likely area to inspect |
|---|---|
Missing Access-Control-Allow-Origin | API response path, error handler, or proxy header configuration |
| Preflight returns 401, 403, or 404 | OPTIONS routing, authentication middleware, or gateway rules |
| Method is not allowed | Access-Control-Allow-Methods and server route configuration |
| Request header is not allowed | Access-Control-Allow-Headers and the browser’s preflight request |
| Credentials error | Specific origin, credential mode, cookies, and credential response header |
| Works in an API client but not the browser | Browser CORS enforcement, not necessarily API availability |
| Request follows a redirect | API URL, HTTPS redirect, proxy routing, and preflight redirect behavior |
Also check for duplicate or conflicting CORS headers. For example, a proxy may add one origin value while the application adds another. The browser may reject the combined response even though each layer appears reasonable in isolation. Inspect the final response received by the browser, not only the application’s local logs.
If the response body contains sensitive data, treat CORS as an access-control boundary for browser-readable responses, not as a general API security system. Continue to use authentication, authorization, input validation, rate controls, and appropriate transport security. CORS configuration should express which browser origins are trusted; it should not replace permission checks inside the API.
Common mistakes
- Using
mode: "no-cors"as a fix. This can produce an opaque response that JavaScript cannot read. It does not make a normal authenticated API response available to the frontend. - Allowing every origin in production without understanding the data. Broad development settings can be useful temporarily, but production access should reflect the applications that are intended to call the API.
- Allowing the method but not the headers. Adding
POSTto allowed methods does not automatically permit anAuthorizationheader or a particular content type. - Fixing only successful responses. Authentication failures, validation errors, and server exceptions can still appear as CORS failures if their responses omit the required headers.
- Testing the wrong origin. The origin is generated by the browser. Do not infer it from the API hostname; read the
Originrequest header and the page URL. - Adding CORS headers in the frontend. The browser expects permission from the server response. A request header supplied by frontend JavaScript cannot grant that permission.
- Changing several layers at once. Record the original request, preflight response, and route before modifying the framework, proxy, and frontend together. Small, isolated changes make the cause easier to identify.
For a wider browser debugging process, use the frontend debugging checklist. If the request contains a token, the JWT decoder guide explains how to inspect claims and expiration safely without treating decoded data as proof of authorization.
When to revisit
Recheck CORS whenever the frontend hostname, API hostname, port, protocol, authentication method, or deployment path changes. It is especially useful to repeat this checklist before releasing a new environment, moving an API behind a gateway, introducing a new frontend application, or changing from cookie sessions to bearer tokens.
Keep a small browser test for each supported origin and representative request type: a simple read, an authenticated request, and a request that triggers preflight. Review the browser Network panel after infrastructure or framework changes rather than assuming existing middleware still handles every response path.
When a failure returns, work in this order:
- Identify the exact page origin and API URL.
- Determine whether the browser sent an OPTIONS preflight.
- Read the final response status and CORS headers.
- Compare requested methods and headers with the server’s allowed values.
- Check credentials, redirects, proxies, and error responses.
- Retest one request after one configuration change.
This method turns an intimidating “blocked by CORS” message into a concrete request-and-response investigation. For related API design and release checks, see the API development checklist, and use the developer tools guide to organize practical debugging utilities.