Same-origin policy, CORS headers & credentials
Understand CORS
Lesson 15 of 20 · 12 min · Module 4: Requests you can trust
Browsers stop pages from reading answers that come from other origins. CORS is how a server says which other origins may read its answers, and one careless CORS header can hand your users’ data to any website.
Start this lesson →Free. No installation: you write and run the code in your browser, and it is checked automatically.
By the end, you’ll be able to…
- Explain the same-origin policy and what CORS relaxes
- Allow only exact, known origins
- Combine CORS with cookies safely
The idea
An origin is a scheme, host and port. A page on https://learn.example calling https://api.learn.example is cross-origin, so the browser lets it read the answer only if the server replies with Access-Control-Allow-Origin naming that origin. Requests that include cookies also need Access-Control-Allow-Credentials: true, and then the origin may never be *. Copying whatever Origin arrives into the answer is the classic mistake.
Origin: https://learn.example ← request
Access-Control-Allow-Origin: https://learn.example ← answer
Access-Control-Allow-Credentials: true
Vary: Origin
01CORS is about reading
Many cross-origin requests are sent anyway; CORS decides whether the page may read the answer. That is why CORS is not a CSRF defence: a forged POST still reaches the server and changes data.
02Match origins exactly
Checks such as origin.endsWith('learn.example') also accept evil-learn.example, and startsWith accepts learn.example.evil.com. Compare against a list of complete origins.
console.log('https://evil-learn.example'.endsWith('learn.example'));true03Vary: Origin keeps caches honest
When the answer depends on the Origin header, say so with Vary: Origin. Otherwise a shared cache may hand one origin’s permission to another.
Your turn
corsHeaders(requestOrigin, { allowedOrigins = [], credentials = false }). It always returns Vary: Origin. When the origin is in allowedOrigins exactly, add Access-Control-Allow-Origin with that origin, and Access-Control-Allow-Credentials: true when credentials is set. When allowedOrigins contains * and credentials is not set, add Access-Control-Allow-Origin: *. Never allow a missing origin, an empty one, or null.What the checks look for
- Allowed origins are named exactly
- Other origins get no permission
- Look-alike origins are refused
- A public API may use * without cookies
- * is never combined with cookies
- The null origin is never trusted
- Requests without an origin get no permission
- The tester shows the browser’s decision
Take this with you
Allow exact origins from a list, never copy the request’s origin, and never use * with cookies.
Go a little further
Support all subdomains of learn.example by parsing the origin with new URL() and checking its protocol and hostname exactly, never with endsWith on the whole string.