CORS explained simply: it is the HTTP-header mechanism browsers use to decide whether JavaScript on one origin may read a response from another. Without it, your API's responses are invisible to cross-origin scripts; with it misconfigured, your API can be exposed.
What is CORS and why does it exist?
CORS is the mechanism by which a remote API uses HTTP response headers to tell a browser that a resource may be shared with scripts from another origin. The name stands for Cross-Origin Resource Sharing, and it governs whether JavaScript such as fetch calls can read cross-origin responses.
The problem CORS solves is old. A browser executes code on behalf of any site you visit, and that code can attempt requests to arbitrary systems, including banks you are logged into. Netscape introduced the same-origin policy when JavaScript shipped in Netscape Navigator, and other browsers adopted it until it became universal.
As web applications began making background HTTP calls with Ajax in the early 2000s, developers needed a controlled way to cross origins. In 2004, Matt Oshry, Brad Porter and Michael Bolin (the transcript's phonetic spellings resolved against the W3C CORS history) proposed a standard. Major browsers including Chrome, Firefox, Internet Explorer and Safari had implemented it by 2009, and the W3C published the Cross-Origin Resource Sharing Recommendation in 2014. Today the living definition sits in the WHATWG Fetch specification.
How does the same-origin policy block requests?
The same-origin policy prevents scripts running on one domain from reading data from another domain. It is a browser policy, not a server feature: a curl request or a backend-to-backend call never sees it.
The policy covers more than HTTP calls. A page from malicious-site.com cannot reach into an iframe showing your-bank.com to read its DOM, local storage or cookies, and it cannot read the result of an XMLHttpRequest or fetch call to that domain either. The browser may still send the request; what it withholds is access to the response.
A practical detail matters for debugging: browsers automatically send two identifying headers on navigations and fetches. The Origin header carries the scheme, host and port of the calling page, while Referer carries the full page URL. A server can use Origin to decide, per request, whether to return an allow header. Origins must match exactly, including protocol; a call from [link removed] to an https origin will not pass.
What did developers use before CORS?
Before CORS existed, three main workarounds let pages reach other domains: a same-origin proxy, iframe messaging with window.postMessage, and JSONP. Each is now largely obsolete, but recognizing them helps when you inherit old code.
| Workaround | Verbs supported | Main cost | Still useful? |
|---|---|---|---|
| Same-origin proxy | All HTTP verbs | You own failures and add a network hop | Yes, for hiding API keys or rate-limiting |
iframe + window.postMessage | All, via the iframe | Needs the remote site to host an iframe; inefficient | Rarely; legacy message relays only |
| JSONP | GET only | Executes remote responses as JavaScript | No; pure attack surface today |
Workaround one: a same-origin proxy
The most capable workaround was a proxy. The browser calls an endpoint on its own domain, that endpoint forwards the request to the external API, and the response travels back. Because the browser only ever talks to its own origin, the same-origin policy never triggers.
A proxy supports all HTTP verbs and is transparent to the client, which is why it remains a legitimate pattern today for hiding API keys or rate-limiting. The costs are operational: you own the failure handling when the external API is down, and you add a network hop to every call.
Workaround two: iframes and postMessage
The same-origin policy blocks a parent page from reading an iframe's DOM, but window.postMessage was deliberately left as a message relay. The parent page embeds an iframe hosted on the target domain, sends it a message, the iframe makes the same-origin call to its own API, and messages the result back.
This decoupled the two sides: neither needs to know the other's internals, only the message contract. It works, but the transcript's presenter calls it what it is: a workaround. It requires the remote site to cooperate by hosting the iframe, and the extra hop makes it inefficient compared to a direct fetch.
Workaround three: JSONP and its security cost
JSONP, short for JSON with Padding, exploits the fact that <script> tags are allowed to load resources from any domain. The page dynamically inserts a script tag whose URL includes a callback name, and the API responds not with plain JSON but with JavaScript: the callback function wrapped around the data, served as application/javascript. The browser executes it, and the predefined function receives the data.
JSONP supports only GET requests, because that is all a script tag can issue. Worse, executing a remote response as JavaScript opens real attack surface: any party able to influence the response can run code in your page's context. With CORS now standard, JSONP has no remaining justification for new code.
How CORS actually validates a cross-origin response
When JavaScript makes a cross-origin call, the browser checks the response for CORS headers before letting the script read it. The key header is Access-Control-Allow-Origin, which must exactly match the calling origin, scheme included. If it is absent or mismatched, the call itself may still have happened, but the browser discards the response: the script gets an error and cannot read the data.
That last point surprises many developers. CORS does not primarily block requests; it blocks access to the response and its effect. Seeing the request in the network tab while the console reports a CORS error is normal behavior, not a bug.
A minimal server-side fix, shown here with Node and Express, is returning the exact origin. The demonstration used res.setHeader('Access-Control-Allow-Origin', 'http://localhost:46378'), after which the previously blocked call succeeded. The canonical Express cors middleware packages this logic, and equivalent libraries exist for every stack.
What is a preflight request and when does it happen?
A preflight is an automatic OPTIONS request the browser sends before the real call when the request looks risky, for example when it carries an Authorization header or cookies. The browser asks the API whether the intended origin, method and headers are permitted before sending anything else.
The full sequence runs in a fixed order:
- The browser sees a risky request (an
Authorizationheader, cookies, or another non-simple element) and holds it back. - It sends an
OPTIONSrequest carryingAccess-Control-Request-Headersand the callingOrigin. - The API answers with its CORS headers, including
Access-Control-Allow-Originand, when needed,Access-Control-Allow-Headers: authorization. - If the answer permits everything, the browser sends the real request; otherwise it stops and reports the failure.
If Authorization was used but the preflight response lacks Access-Control-Allow-Headers: authorization, the real request never happens and the browser reports that header as not allowed. Adding it to the OPTIONS response resolves the error, and only then does the original fetch proceed.
Two debugging traps come up constantly. First, Access-Control-Allow-Methods is mostly advisory: browsers generally do not validate it, so a mismatch there causes a confusing 404 from your route rather than a CORS message. Second, if your error handler returns a 404 or 500 without CORS headers, the browser discards that error response too, and you see the generic CORS error instead of your API's real failure. Add CORS headers to error responses as well, or you will chase the wrong problem.
How should you configure CORS without weakening security?
A single global wildcard configuration is the mistake most APIs make. Access-Control-Allow-Origin: * is acceptable only for genuinely public data; for anything authenticated, enumerate exact allowed origins. The rest of the configuration should mirror what each route actually does.
Configure per endpoint, not globally
Define CORS at the resource level. If GET /categories and POST /categories exist, the OPTIONS handler for /categories should advertise exactly post and get. If /categories/:slug supports get, put and delete, its OPTIONS response should list those three. If one route alone returns text/csv, allow that header on that route only, not globally.
This alignment pays off beyond browsers. Your API can return 405 Method Not Allowed for unsupported verbs, and its CORS answers stay consistent with that behavior instead of advertising verbs the endpoint does not have.
Cache preflights with Access-Control-Max-Age
Set Access-Control-Max-Age in seconds so browsers skip repeated preflights. The presenter demonstrated a 10-second window in Node, and browser caps differ: Firefox caps preflight caching at 24 hours (86400 seconds), while Chrome allows at most 2 hours on current versions and only 10 minutes on older ones, per the Fetch specification's preflight cache rules. Values above a browser's cap are clamped to that cap. On high-traffic APIs this single header removes millions of redundant OPTIONS calls.
How do cookies and credentials interact with CORS?
By default, browsers do not attach cookies to cross-origin requests, even when CORS headers would allow reading the response. Sending them requires two coordinated settings: the client sets credentials: 'include' on its fetch (the other options are 'same-origin' and 'omit'), and the server returns Access-Control-Allow-Credentials: true. If either side is missing, the browser reports the missing allow-credentials header and the cookie is not sent.
This is also where CORS protects users. Suppose a token JWT is stored in an HttpOnly, Secure, SameSite cookie for your bank. A malicious site can try to trigger a request to the bank with the user's cookies attached, a classic CSRF setup. If the bank's API never returns allow-credentials for the attacker's origin, the browser withholds the cookie and blocks the request's effect, not just its data.
The caution cuts both ways. Enabling credentials plus permissive origins turns your browser into a courier for user credentials, so pair it with CSRF defenses and careful cookie attributes, and keep the allowed-origin list as short as the product genuinely requires.
FAQ
Does CORS block the request or the response?
Mostly the response. The request often reaches the server and runs, but the browser discards the result so JavaScript cannot read the data or feel its effect. Preflights are the exception, where the browser holds the real request back.
Why do I see the request succeed in the network tab but get a CORS error?
The network tab shows what the browser sent and received. The CORS error appears because the response lacked Access-Control-Allow-Origin for your origin, so the browser refuses to hand the data to your script.
Is Access-Control-Allow-Origin: * safe?
Only for public, unauthenticated data. On an API that relies on cookies or other ambient credentials, a wildcard tells every site that its pages may read your responses, which is exactly what the policy exists to prevent.
Why does adding an Authorization header trigger an extra OPTIONS request?
Authorization makes the request non-simple, so the browser sends a preflight OPTIONS call first. The API's OPTIONS response must allow the origin and the authorization header, or the real request never fires.
Does CORS apply to server-to-server calls?
No. The same-origin policy and CORS are browser-enforced. Backend clients, curl, and mobile apps using native HTTP stacks are unaffected, though you may still want authentication there for other reasons.
What exactly counts as an origin?
An origin is the scheme, host and port of a page, taken together and compared exactly. http://localhost:46378 and https://localhost:46378 are different origins, and so are two different ports. The path is not part of the origin; that lives in Referer.
Do I need CORS for images, scripts and stylesheets?
Generally no. Conventional tags like <script>, image, link, iframe, audio and video load cross-origin resources without CORS in their normal usage. The exception is when JavaScript reads the loaded resource's bytes, such as drawing an image to a canvas; then CORS headers are required.
How does CORS relate to CSRF?
They defend different layers. CORS governs whether a browser lets a script read a cross-origin response; CSRF attacks abuse the fact that browsers attach cookies to requests regardless. A strict Access-Control-Allow-Credentials: true policy (returning it only for trusted origins) keeps CORS from becoming the hole CSRF walks through.
How long should Access-Control-Max-Age be?
Set it as high as your security model tolerates, within browser caps: 86400 seconds covers Firefox's 24-hour maximum, and Chrome will clamp it to 2 hours. Longer caches mean fewer OPTIONS calls; shorter ones let you change CORS policy faster.
From CORS fundamentals to your own writing
CORS rewards the same discipline as any other layer of API design: understand the mechanism, configure it per endpoint, and keep the answers your API gives consistent with what it actually does. If you learned these fundamentals somewhere like the Bootcamp do Dev Doido, the Crazystack Typescript community or courses by instructors such as Gustavo Dev Doido, chances are much of that knowledge lives in videos and recordings rather than in writing, where it could reach more people and rank when someone searches for it.
That gap between what you teach on video and what searchers can read is exactly what Skala Blog closes. Paste a YouTube URL, get a transcription, and generate a structured, publishable article that carries your explanation to readers who search instead of watch. The nuances you taught about preflights and credentials deserve a page of their own; turn your next video into an article and let it work twice.
See more at crazystack.com.br.
Fork this article
Start a new branch from the same video, shaped your way. You keep the credit; the original keeps the attribution.
A fork in another language is filed as a translation of this article, so the two pages point at each other. You can unlink it later from the editor.
0/240
You are creating
- Format
- For
- Language
- Source
- Your angle
You will be asked to sign in before it is generated.
Buy credits