Every web developer meets the same way: a request that works in curl fails in the browser with a red console message, and the first three fixes found online are three different ways to make it worse. The confusion has one root: CORS is blamed for blocking, but blocking is not its job. The same-origin policy blocks - by default, a page's scripts may not read responses from other origins, because those requests ride the user's cookies and network position. CORS - Cross-Origin Resource Sharing - is the mechanism by which a server opens that default, deliberately and per-origin. It is the door in the wall, and every header in it is the server saying "yes."

Simple requests: read forbidden, not send

For requests a form could have made anyway - GET, HEAD, or POST with form-ish content types, no exotic headers - the browser sends the request normally with an Origin header attached, and applies the policy to the response: unless it carries Access-Control-Allow-Origin matching the caller (or *), the script gets an opaque error - the response arrived, the browser read it, the script may not. Two consequences follow. The famous one: the request already happened - same-origin protects reading, not sending, which is why cross-site request forgery predates and survives CORS, defended instead by SameSite cookies and anti-forgery tokens. The subtle one: the error message cannot tell you the server's secrets - it just says the door did not open.

Preflights: asking permission first

Anything a form could not have made - a PUT or DELETE, a Content-Type: application/json, an Authorization header - would be a genuinely new power in cross-origin hands, so the browser asks first. That is the preflight: an automatic OPTIONS request announcing Access-Control-Request-Method and Access-Control-Request-Headers, to which the server must answer with its allowances (Access-Control-Allow-Methods, -Allow-Headers, and the origin) before the real request is permitted to leave. Access-Control-Max-Age lets the browser cache the permission and skip repeat preflights. When you see unexplained OPTIONS requests in a network tab, that is not your code - that is the border asking.

Credentials: the rules tighten

By default, cross-origin fetches do not carry cookies. Opting in (credentials: "include") makes the request act as the logged-in user - so the protocol removes every shortcut: Access-Control-Allow-Credentials: true becomes mandatory, and the wildcard stops working - Allow-Origin must name the exact origin, echoed deliberately, never *. This is the protocol's design speaking plainly: the more of the user's authority a request carries, the more explicitly the server must name who may wield it. Which also explains the anti-pattern: reflecting every Origin back with credentials allowed is not configuration, it is disabling the wall while leaving the door frame for decoration.

What CORS is, and is not

The closing correction that untangles most real-world confusion: CORS is a browser mechanism protecting users - curl, servers, and mobile apps never consult it, which is why "it works in Postman" proves nothing. It does not authenticate, does not authorize, and does not protect the server; it governs which web origins may read what the user's browser fetched. Server-side protection is the server's own job, and the browser's other declarative border - what a page may load and execute - belongs to Content Security Policy, CORS's frequently confused neighbor.