THIS EXPLANATION
THE ROOM
CDA·56 Computing, Data & AI 6 MIN · 8 STATIONS

CORS in the web

A Socratic walk-through of CORS — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

What is CORS?

When your JavaScript on mysite.com calls fetch("https://api.othersite.com/data"), whose permission actually matters — yours, the other server's, or someone standing between the two? Most people assume that if a reply comes back, it is simply theirs to read. It is worth pausing on that assumption, because the whole of CORS hangs on why it is not quite true.

b

Reasoning it through

REASONING #

Consider that your code and the far server never really speak directly — the browser sits in the middle and carries every request on your behalf. So suppose a stranger's page, opened in the same browser where you are logged into your bank, could quietly fetch yourbank.com/account, riding on your logged-in cookies, and read the reply. What might go wrong there? That precise worry is the thing CORS exists to answer. Notice how the danger only appears because the browser is shared and trusted.

Sit with that middle step, because it is the root of everything else. A cookie is attached by the browser to any request bound for the host that set it, without asking the page that triggered the request whether it deserves that authority. Your logged-in session is therefore not a possession of your bank's page; it is an ambient power the browser wields on behalf of whatever happens to be running in it. If reading cross-origin replies were free, every page you visited while logged in anywhere could read your mail and your balance simply by asking. So the browser adopted a blunt default — the same-origin policy — under which a script may read only what came from its own scheme, host and port.

But blunt defaults break honest cases: a public weather API, a company's own front end talking to its own API on another subdomain. What would you need in order to relax the rule safely? Only one thing: some way for the server whose data is at stake to say so. It cannot be the requesting page that decides, since a hostile page would grant itself permission; and it cannot be the browser guessing, since the browser has no idea which cross-origin reads are innocent. The permission must come from the far end — and that is all CORS is.

One more question then follows on its own. Some requests are dangerous merely to send — a DELETE on a record, a PUT with a JSON body. Should the browser fire those off and only afterwards discover it was not allowed to show you the answer? It does not. For anything outside a small "simple" set it sends an OPTIONS request first, naming the method and headers it intends to use, and withholds the real request entirely unless the answer comes back affirmative.

c

The analogy

THE ANALOGY #
THE FIGURE

Imagine your page lives in apartment A, and it wants a package from store B across town. The browser is the doorman. He will gladly carry your request to store B and bring the reply back to the building — but before handing the package to you, he checks for a note taped to it. The note must say, in effect, "Apartment A is allowed to open this." No note, or a note naming someone else, and the doorman shreds the contents before you ever see them. For unusual errands he telephones ahead first, describing what he is about to be asked to do, and refuses to set out unless the store says it would accept such a visit.

WHERE IT BREAKS DOWN

The doorman does not stop the errand. He carries your request, and store B may well act on it — charge a card, delete a record — before any note is checked; all he withholds is the contents from you. And he guards only browsers: a server, or curl, walks in with no doorman at all.

d

Clarifying the model

THE MODEL #

That note is a response header the far server chooses to send: Access-Control-Allow-Origin. The decision sits with the destination server, openly declaring which origins may read its replies — not with your code, and not with the browser's guess.

The advance telephone call has a name and a shape. It is a preflight: a real OPTIONS request the browser makes on its own initiative, carrying Access-Control-Request-Method and, where relevant, Access-Control-Request-Headers. Anything outside the "simple" set triggers it — a GET or a form-style POST slips through, while a PUT, a DELETE, a custom header like X-Api-Key, or a POST with Content-Type: application/json all provoke one. The server must answer with matching Access-Control-Allow-Methods and Access-Control-Allow-Headers.

Now the misconception worth correcting, which is where most debugging time goes. A CORS error usually does not mean your request was blocked. Unless a preflight failed, the server received it and acted; the browser merely refused to let your script read the answer. That has a consequence people find genuinely surprising: CORS is no defence against a hostile page sending requests to your API. A form on any site can POST to yours with your users' cookies attached and no CORS header will prevent it — which is why cross-site request forgery needs its own defences, and why a write endpoint that trusts a request simply because it arrived is unsafe whatever its CORS configuration says.

And one framing that clears up much of the frustration: CORS does not add protection. The protection was the same-origin policy, which came first and is the default; CORS is the mechanism for relaxing it. So a permissive setting is not turning security on, it is turning a restriction off, and Access-Control-Allow-Origin: * on an endpoint returning anything private is a hole rather than a convenience — which is also why the browser refuses to honour the wildcard at all once credentials are involved.

e

A picture of it

THE PICTURE #
CORS
CORS Time runs top to bottom. The browser always carries the request to the far server and receives the reply -- what changes is whether it lets your page read that reply, which depends entirely on the far server's Access-Control-Allow-Origin header. The alt box shows the two outcomes. Note what the diagram does not draw, because it is the simple case: for a DELETE or a JSON POST there is an earlier OPTIONS exchange above the first arrow, and if that one fails the second arrow never happens at all. {"generator":"mermaid-svg-renderer@3.2.1","source":"../Socrates/.diagram-cache/_src/cors-in-the-web.md","sourceIndex":1,"sourceLine":4,"sourceHash":"fdf01661c04fd1c44cdc4750184be4558fae52a4657d86a1e9b612512e627fb8","diagramType":"sequence","layoutVariant":"source","repairedDuplicateIds":[],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":914,"height":652},"qa":{"passed":true,"findings":[]}} api.othersite.com 01 Browser (doorman) 02 Page (mysite.com) 03 alt [header names your origin] [missing or wrong origin] fetch() request (carries your cookies) reply + Access-Control-Allow-Origin? hands over the reply blocks your script from reading it
KINDSlifelineparticipantalternativemessage

How to readTime runs top to bottom. The browser always carries the request to the far server and receives the reply — what changes is whether it lets your page read that reply, which depends entirely on the far server's Access-Control-Allow-Origin header. The alt box shows the two outcomes. Note what the diagram does not draw, because it is the simple case: for a DELETE or a JSON POST there is an earlier OPTIONS exchange above the first arrow, and if that one fails the second arrow never happens at all.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

CORS is not your code being punished. It is a doorman protecting the person whose browser is being borrowed, releasing a cross-origin reply only when the far server has plainly said "this origin may look." The restriction it governs was already there; CORS is only the vocabulary for lifting it. And because it governs reading rather than sending, it protects your users from other people's pages — not your API from anybody. Debugging it means inspecting the far server's response headers, not the network.

g

Where to go next

ONWARD #
  • What credentials (cookies) change about that note, and why credentials: "include" raises the bar.
  • Why a wildcard Access-Control-Allow-Origin: * stops being allowed the moment credentials are involved.
  • How preflight caching (Access-Control-Max-Age) reduces the number of scout trips.
  • How cross-site request forgery attacks the gap CORS deliberately leaves open.
h

Key terms

TERMS #
TermWhat it means
Originthe scheme + host + port of a page (e.g. https://mysite.com); "cross-origin" means a different one.
Same-origin policythe browser's default rule that a script may read only responses from its own origin; CORS relaxes this rather than replacing it.
Access-Control-Allow-Originthe response header by which the far server names which origins may read its reply.
Preflightan automatic OPTIONS request the browser sends first for non-simple requests, to ask permission before the real one.
Simple requestroughly a GET, HEAD, or form-style POST with no unusual headers; these skip the preflight.
Cross-site request forgeryan attack that abuses ambient cookies to make a request happen, without ever needing to read the reply, and therefore untouched by CORS.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4