EXPLAINER

CORS

CORS is a browser rule for cross-origin requests. The server must opt in with response headers. It is not authentication, and it is not a barrier to non-browser clients. Fix it by allowing the origins you intend, not by reflecting every origin with credentials.

Short answer: CORS (Cross-Origin Resource Sharing) is how a browser asks a server whether a page from another origin may read a response. Configure the origins you own. CORS is not authentication, and tools like curl ignore it.

What it is

CORS (Cross-Origin Resource Sharing) is a browser mechanism. A web page served from one origin (scheme, host and port together) that calls an API on another origin is making a cross-origin request. Browsers allow some of these and, for others, first ask the server whether it opts in. The server opts in with headers such as Access-Control-Allow-Origin. If the header is missing or does not match, the browser hides the response from your JavaScript and shows a CORS error. The request often still reached the server.

CORS is not a server firewall. Authentication versus Authorization is a different problem. curl, mobile apps and other servers do not enforce CORS. If your security depends on CORS, you have no security against any client that is not a browser.

Why and when it is used

You meet CORS when the Frontend and Backend are on different origins: localhost:5173 calling localhost:3000, or app.example.com calling api.example.com. Same origin (the page and the API share scheme, host and port) does not need CORS for ordinary requests. You configure CORS so the browser will let your front end read the response.

Do not "fix" CORS by allowing * and credentials. Browsers reject that combination, and a reflection of any origin with credentials lets any website call your API as the user if the user has a cookie. Allow a list of origins you control. Do not disable CORS in the browser as a development habit; you will not notice the production misconfiguration.

How it works

A simple request (some GETs and POSTs with simple headers) is sent, and the browser exposes the response to JavaScript only if the allow-origin header matches. A preflight is an OPTIONS request sent first when you use methods or headers the browser considers non-simple, such as PUT or Authorization or a JSON content type in many cases. The server must answer the preflight with the allowed methods, headers and origin. If it does not, the real request never runs. That is why a route that works in curl fails from the browser, and why the failure mentions OPTIONS.

Access-Control-Allow-Credentials: true is required if you send cookies. Then the origin must be explicit, not *. The server should vary the allow-origin header by the request's Origin only when that origin is on the allow-list. Echoing the request origin blindly is the bug.

Technologies and dependencies

The browser, your API (Express middleware or the equivalent), and a correct list of front-end origins per environment. Production and localhost are different origins. REST APIs for Application Development error handling should still return JSON your code can read once CORS allows it. WebSockets have a related origin check on the upgrade, but they do not use these headers the same way.

How to get started

  1. Decide the front-end origin (for example http://localhost:5173 in dev and https://app.example.com in prod).
  2. Configure the API to allow only that origin, the methods you use, and the headers you send (Authorization, Content-Type).
  3. From the browser, make the call. If it fails, read the console: it usually says which header was missing.
  4. Repeat with credentials if you use cookies. Set allow-credentials and an explicit origin.
  5. Call the same URL with curl and notice that it works even when the browser does not. Remember that for the security review.
  6. Add the production origin when you deploy. Do not leave * in production because dev was easy.

Handle OPTIONS in the same auth-free way your middleware expects. A preflight that requires a bearer token will fail because the preflight does not send one.

Cautions and trade-offs

Allowing every origin is fine for a truly public read API with no cookies and no secrets in the response. It is wrong for anything user-specific. Putting secrets in a response and allowing every origin lets any site read them if it can induce the user's browser to call you with credentials.

CORS errors are also caused by the server crashing before it adds headers, or by a proxy stripping headers. Look at the actual response in the network panel. And do not spend a day on CORS when the status code is 401. Fix authentication, then let the browser see the error by configuring CORS on the error response too. A 401 without CORS headers looks like a CORS bug and is not.