The Same-Origin Policy
Browsers enforce the Same-Origin Policy: JavaScript running on one origin cannot read responses from a different origin. Two URLs have the same origin only if they share the same scheme, host, and port.
| URL A | URL B | Same Origin? | Why |
|---|---|---|---|
https://example.com/a |
https://example.com/b |
Yes | Same scheme, host, port |
https://example.com |
http://example.com |
No | Different scheme (https vs http) |
https://example.com |
https://api.example.com |
No | Different host (subdomain counts) |
https://example.com |
https://example.com:8080 |
No | Different port |
CORS is the mechanism that lets a server opt in to allowing cross-origin requests. The server sends specific headers that tell the browser: "requests from this origin are allowed."
Simple Requests vs Preflight Requests
Browsers classify cross-origin requests into two categories:
Simple requests (no preflight) — must meet all of these:
- Method is GET, HEAD, or POST
- Only "safe" headers (Accept, Accept-Language, Content-Language, Content-Type)
- Content-Type is one of:
text/plain,multipart/form-data, orapplication/x-www-form-urlencoded
Preflighted requests — anything else (PUT, DELETE, custom headers, application/json, etc.). The browser sends an OPTIONS request first to ask the server for permission.
Simple Request (GET with safe headers):
Browser (app.com) Server (api.other.com)
│ │
│ GET /data HTTP/1.1 │
│ Origin: https://app.com ───────────────────▶│
│ │
│ HTTP/1.1 200 OK │
│ Access-Control-Allow-Origin: https://app.com│
│ [data] ◀────────────────────────────────────│
│ │
Preflighted Request (PUT with JSON):
Browser (app.com) Server (api.other.com)
│ │
│ 1. Preflight (automatic) │
│ OPTIONS /data HTTP/1.1 │
│ Origin: https://app.com │
│ Access-Control-Request-Method: PUT │
│ Access-Control-Request-Headers: Content-Type│
│ ────────────────────────────────────────────▶ │
│ │
│ HTTP/1.1 204 No Content │
│ Access-Control-Allow-Origin: https://app.com│
│ Access-Control-Allow-Methods: GET, PUT, POST│
│ Access-Control-Allow-Headers: Content-Type │
│ Access-Control-Max-Age: 86400 │
│ ◀──────────────────────────────────────────── │
│ │
│ 2. Actual request (only if preflight passes)│
│ PUT /data HTTP/1.1 │
│ Origin: https://app.com │
│ Content-Type: application/json │
│ {"key": "value"} ─────────────────────────▶ │
│ │
│ HTTP/1.1 200 OK │
│ Access-Control-Allow-Origin: https://app.com│
│ [response] ◀───────────────────────────────│
│ │
CORS Headers
| Header | Direction | Purpose |
|---|---|---|
Origin |
Request | The origin making the request |
Access-Control-Allow-Origin |
Response | Which origins are allowed (* = any, or a specific origin) |
Access-Control-Allow-Methods |
Response (preflight) | Which HTTP methods are allowed |
Access-Control-Allow-Headers |
Response (preflight) | Which request headers are allowed |
Access-Control-Allow-Credentials |
Response | Whether cookies/auth headers are allowed (true) |
Access-Control-Max-Age |
Response (preflight) | How long (seconds) to cache the preflight result |