Same-Origin Policy and CORS Explained

Intermediate
11 min

Same-Origin Policy and CORS Explained

The same-origin policy (SOP) is the most important security boundary in the browser. It stops a script on one website from reading data that belongs to another, which is why a blog in one tab cannot read your bank balance from another. CORS (Cross-Origin Resource Sharing) is the controlled way to relax that boundary when you want it relaxed.

In this lesson you will learn what an origin is, exactly what SOP blocks, how the CORS handshake works, how to configure it safely in Express, and the misconfigurations that turn CORS into a vulnerability.

What Is an Origin?

An origin is the combination of scheme, host and port. Two URLs share an origin only if all three match.

| URL | Same origin as https://shop.example.com/cart? | Why | |---|---|---| | https://shop.example.com/checkout | Yes | Only the path differs | | http://shop.example.com/cart | No | Different scheme | | https://api.shop.example.com/ | No | Different host (subdomain) | | https://shop.example.com:8443/ | No | Different port |

What the Same-Origin Policy Blocks

SOP is a read restriction, not a send restriction. A page on evil.example.com can still:

  • Load images, scripts and stylesheets from any origin.
  • Submit an HTML form to any origin (the browser sends the user's cookies).
  • Fire a fetch() request to any origin.

What it cannot do is read the response of a cross-origin fetch() or the DOM of a cross-origin iframe. The request still reaches your server; only the answer is hidden. That is why SOP alone does not prevent CSRF: the attacker does not need to read the reply to cause damage.

How the CORS Handshake Works

CORS lets a server declare which foreign origins may read its responses. For "simple" requests (GET, HEAD, POST with form content types and no custom headers) the browser sends the request with an Origin header and checks the answer:

http
GET /api/profile HTTP/1.1 Origin: https://app.example.com HTTP/1.1 200 OK Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Credentials: true Vary: Origin

If the Access-Control-Allow-Origin value does not match, the browser discards the response and the script sees a network error. Anything more complex (JSON bodies, Authorization headers, PUT or DELETE) triggers a preflight: an OPTIONS request that asks permission before the real request is sent. The server answers with Access-Control-Allow-Methods, Access-Control-Allow-Headers and Access-Control-Max-Age.

Configuring CORS in Express

The sample code above shows a safe configuration using the cors package. The rules it encodes:

  • Keep an explicit allowlist of origins; never reflect whatever Origin header arrives.
  • When credentials: true is set, the browser rejects a wildcard *, so an explicit origin is mandatory.
  • Send Vary: Origin (the package does this) so caches do not serve one origin's headers to another.
  • Restrict methods and allowedHeaders to what the frontend actually uses.

On the client, cookies are only attached to cross-origin requests when you ask for them:

javascript
const res = await fetch("https://api.example.com/api/profile", { credentials: "include" // send cookies; server must allow this origin explicitly }); console.log(await res.json()); // { name: "Ada" }

Common Mistakes

  • Reflecting the Origin header plus credentials lets any site read authenticated responses.
  • Trusting null. Sandboxed iframes and local files send Origin: null; allowing it means allowing everyone.
  • Treating CORS as server protection. curl ignores CORS completely, so authentication and authorization must still happen on the server.
  • Loose regex allowlists. /example\.com$/ also matches notexample.com. Anchor the whole origin:
javascript
const ok = /^https:\/\/([a-z0-9-]+\.)?example\.com$/.test(origin);
Quick Quiz
Question 1 of 2

Which pair of URLs shares the same origin?

Key Takeaways

  • An origin is scheme + host + port; anything else is cross-origin.
  • SOP blocks reading cross-origin responses, not sending requests, so it does not replace CSRF defenses.
  • CORS is opt-in by the server through Access-Control-Allow-Origin and related headers; complex requests are preceded by an OPTIONS preflight.
  • Use an explicit origin allowlist, never reflect the incoming origin, and never combine * with credentials.
  • CORS is enforced by browsers only; server-side authentication and authorization remain mandatory.

Next lesson: Cookies and Session Management: HttpOnly, Secure and SameSite — the cookie attributes that keep sessions from being stolen or forged.

Same-Origin Policy and CORS Explained - Cyber Security | CodeYourCraft | CodeYourCraft