If you have ever seen “blocked by CORS policy” in your browser console, you already know the frustration: your code looks correct, the API works in Postman, yet the browser refuses the response. This article explains what CORS actually is, why the error happens, and how to fix it properly on the server so you stop guessing.
What CORS Really Is
CORS stands for Cross-Origin Resource Sharing. It is a browser security rule built on top of the Same-Origin Policy. An “origin” is the combination of scheme, host, and port, for example https://app.example.com. When your page on one origin asks a browser to fetch data from a different origin, the browser checks whether the server gives permission through response headers. No permission, no access.
The key point most people miss: CORS is enforced by the browser, not the server. The server still receives the request and often still runs it. The browser simply hides the response from your JavaScript when the headers do not allow it. That is why the same request succeeds in a terminal or in Postman, which do not enforce CORS.
Why the Restriction Exists
Without this rule, any website you visit could silently call an API where you are logged in and read your private data using your session cookies. CORS forces the target server to explicitly say which origins are allowed to read its responses. It protects users, even though it annoys developers.
Simple Requests vs Preflight Requests
The browser treats requests in two ways. A “simple” request (a basic GET or a POST with common form content types) is sent directly, and the browser checks the response headers afterward.
Anything more complex triggers a preflight: the browser first sends an OPTIONS request to ask permission before sending the real one. Preflight is triggered by things like a PUT or DELETE method, a custom header such as Authorization or X-Api-Key, or a JSON content type. If the preflight response does not approve the method and headers, the real request never runs.
The Headers That Actually Matter
| Header | What it does |
| Access-Control-Allow-Origin | Which origin may read the response |
| Access-Control-Allow-Methods | Which HTTP methods are permitted |
| Access-Control-Allow-Headers | Which request headers are permitted |
| Access-Control-Allow-Credentials | Whether cookies or auth headers are allowed |
| Access-Control-Max-Age | How long the browser caches the preflight result |
The fix is almost always adding the correct values to these response headers on the server that owns the API.
A Real Scenario
A frontend on https://dashboard.mysite.com calls an API on https://api.mysite.com and sends an Authorization header with a token. The request fails with a CORS error. What happened: the custom header triggered a preflight, and the API server did not answer the OPTIONS request with Access-Control-Allow-Headers: Authorization. Adding that header, plus Access-Control-Allow-Origin: https://dashboard.mysite.com, fixes it. The frontend code never needed to change.
Common Mistakes and How to Fix Them
Using a wildcard with credentials. Setting Access-Control-Allow-Origin: * together with cookies does not work. The browser rejects the combination. Fix: echo back the specific requesting origin from an allowlist instead of using the wildcard.
Editing the frontend to fix a server problem. Adding headers to your fetch request does not grant permission. Only the target server can allow CORS. Fix: change the API server or its gateway.
Forgetting the OPTIONS route. Many frameworks route OPTIONS separately. If it returns 404 or 405, preflight fails. Fix: make sure OPTIONS returns 200 or 204 with the CORS headers.
Disabling CORS in the browser. Launching Chrome with security flags may make it “work” on your machine, but it fails for every real user. Fix: solve it on the server.
Confusing CORS with a network error. A blocked preflight can look like a failed request. Check the console message and the OPTIONS response, not just the failed status.
Action Checklist
- Open the browser Network tab and confirm whether an OPTIONS preflight was sent.
- Read the exact CORS message; it names the missing permission.
- Set Access-Control-Allow-Origin to your real frontend origin, not a guess.
- Add every custom header (like Authorization) to Access-Control-Allow-Headers.
- List all methods you use in Access-Control-Allow-Methods.
- If you send cookies, set Access-Control-Allow-Credentials: true and drop the wildcard.
- Ensure OPTIONS returns a 2xx status.
Conclusion
CORS is not a bug to defeat; it is a permission system to configure. Once you accept that the browser is only enforcing what the server declares, the fix becomes mechanical: set the right response headers on the API that owns the data. Next step: reproduce your error, watch the preflight in the Network tab, and correct the specific header it complains about.
FAQ
Why does my API work in Postman but not the browser?
Postman and curl do not enforce CORS. Only browsers do. The request itself is fine; the browser is blocking your JavaScript from reading the response because the permission headers are missing.
Is a CORS proxy a good solution?
A proxy can be useful for quick tests or when you truly cannot change the target server. For your own API, fix the headers directly. Public proxies are unreliable and can expose your data, so avoid them in production.
Does CORS make my API secure?
No. CORS only controls which web pages can read responses in a browser. It is not authentication or authorization. Server-side access control is still required.
Why did adding the wildcard break things when I use cookies?
The browser forbids sending credentials to a wildcard origin. You must specify the exact origin and set the credentials header to true.
References
- MDN Web Docs: Cross-Origin Resource Sharing (CORS)
- MDN Web Docs: Same-Origin Policy