JavaScript CORS Table
| Piece | What it does | Field note |
|---|---|---|
Access-Control-Allow-Origin | Who may read the response | The SERVER's grant - * or one exact origin; no lists |
Simple vs preflighted | GET/POST-basic vs everything else | Custom headers or PUT/DELETE trigger the OPTIONS preflight |
OPTIONS preflight | The automatic permission ask | Browser asks BEFORE sending; cache via Access-Control-Max-Age |
Access-Control-Allow-Headers | Preflight header whitelist | Authorization is NOT simple - adding it preflights every request |
Access-Control-Allow-Credentials | Cookies cross-origin | true requires an EXACT origin - * with credentials is refused |
CORS errors in console | The response existed | Browser got it and hid it - CORS blocks READING, not sending |
no-cors mode | Opaque responses | Sends it, gives you an unreadable blob - the escape hatch that barely works |
Proxy pattern | Your server fetches | Same-origin to your backend, which talks to the third party - the production fix |
CORS is the browser's enforcement of the SERVER's grants: under the same-origin policy, a cross-origin request usually FLIES, the response arrives at the network layer, and the browser HIDES it from your JavaScript unless the response carries Access-Control-Allow-Origin naming your origin.
Bottom line: CORS errors mean the response existed and was confiscated - which is why the fix is always server-side (the client cannot grant itself permission) and why 'it works in Postman' is expected: Postman is not a browser and ignores the policy entirely.
The honest part: adding an Authorization header or a custom header silently upgrades your request to PREFLIGHTED - the browser first sends an automatic OPTIONS ask, and only on approval does the real request go. Teams discover this in production when auth headers turn every request into two.
How to use
- Fix CORS on the server: Access-Control-Allow-Origin: https://your.app on the API response - one exact origin beats * the moment credentials are involved.
- Tame the preflight: know that custom headers (Authorization, X-*) trigger OPTIONS; cache approvals with Access-Control-Max-Age to stop the double-request tax.
- Route uncooperative third parties through your backend: the browser calls your same-origin API, your server calls the third party - CORS never applies server-side.
Frequently asked questions
If CORS blocks my request, did the request even happen?
Yes - this is the most misunderstood part. Under the same-origin policy, a simple cross-origin request is SENT; the server receives it, processes it, and responds. The browser then inspects the response headers and, finding no Access-Control-Allow-Origin for your origin, refuses to hand the response to your JavaScript - surfacing the familiar red console error. Side effects happen (a POST may have written data), which is why preflights exist: for anything with side effects or exotic headers, the browser asks permission with OPTIONS FIRST, before the real request leaves. Never rely on CORS as a security wall for state-changing endpoints - it protects readers, not writers.
Why did adding an Authorization header break my working request?
It upgraded you to preflighted. 'Simple' requests - GET, HEAD, POST with a few basic content types and no custom headers - skip permission checks and just get their responses inspected. The moment you add Authorization, a custom X- header, or use PUT/DELETE, the browser must first ask the server 'may I send this?' via an automatic OPTIONS preflight carrying Access-Control-Request-Headers. If the server does not answer that OPTIONS with matching Allow-Headers (Authorization included), the real request never fires. The fixes: server answers the preflight properly, and Access-Control-Max-Age caches the approval so the double-request tax is paid once.
Why does Access-Control-Allow-Origin: * fail with credentials?
Because the wildcard cannot be trusted with user-scoped data: * tells every origin on the internet it may read the response, and cookies riding along would leak user identity to all of them. The credential contract therefore demands an EXACT origin echo: the server reads the request's Origin header and reflects that one value back, with Access-Control-Allow-Credentials: true. The same rule applies at the cookie level - SameSite=None cookies need Secure. The server-side code is three lines; the security property is that only origins you explicitly list get credentialed access.
What is the production pattern for third-party APIs without CORS headers?
Your own backend becomes the proxy. The browser calls your same-origin endpoint (/api/weather), your server fetches the third-party URL - server-to-server requests have no origin policy, CORS is a BROWSER enforcement - and returns the data. This is also where the API key belongs: shipping a third-party key in frontend code publishes it to every visitor, while the proxy keeps it server-side. Frontend-only workarounds (no-cors mode returning an opaque unreadable response, or public CORS proxy services) range from useless to a supply-chain risk; the backend proxy is the pattern that actually ships.