# Same-origin policy and CORS

> The browser keeps pages from reading other origins’ responses; CORS headers are how a server opts in.

Concept · The browser · https://levelcode.org/concepts/same-origin-policy-cors

An **origin** is scheme + host + port: `https://app.example.com:443`. Change any of the three and it's a different origin. Note that `https://example.com` and `https://api.example.com` are different origins.

The **same-origin policy** is the browser's core security rule: a page can *send* requests almost anywhere, but script can't *read* the response from another origin unless that origin allows it. Without the rule, any site you visit could `fetch('https://yourbank.com/account')` with your cookies attached and read your balance.

## CORS: the server opts in

**Cross-Origin Resource Sharing** is a set of response headers through which a server tells the browser which other origins may read its responses:

```http
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
```

For anything beyond a "simple" request, such as `PUT`/`DELETE`, a JSON `Content-Type`, or custom headers like `Authorization`, the browser first sends a **preflight**:

```http
OPTIONS /orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type, authorization
```

The server must answer with matching `Access-Control-Allow-Methods` and `-Headers`. `Access-Control-Max-Age` lets the browser cache the answer so it doesn't send a preflight every time.

## Misconceptions

- **CORS doesn't protect your server.** It protects *users' browsers* from malicious pages. `curl`, scripts and other servers ignore it completely. You still need authentication and authorization.
- **A CORS error doesn't mean the request didn't happen.** A simple `POST` is sent and processed; the browser just hides the response. Protect state-changing endpoints against CSRF with `SameSite` cookies or tokens. See [sessions vs tokens](https://levelcode.org/concepts/sessions-vs-tokens).
- **Don't reflect any `Origin` with credentials allowed.** That turns the policy off for every site. Keep an allowlist of origins.
- **`*` can't be combined with credentials.** The browser rejects that combination.

## Often you don't need it

If the frontend and API share an origin, for example with the API under `/api` behind the same host or a dev-server proxy, there's no cross-origin request and nothing to configure. When you do need CORS, set it in one place: [middleware](https://levelcode.org/concepts/middleware) or your [API gateway](https://levelcode.org/roadmaps/devsecops/api-gateways).

The same-origin policy limits what *other* sites can read. [XSS](https://levelcode.org/concepts/xss-and-csp) is the opposite problem: an attacker's script running *inside* your origin, where the policy doesn't help.
