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'));
Output
true

03Vary: 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

Replace the server function 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
Try it in the editor →

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.

Learn Web Security Foundations for free

20 short lessons with real code and instant checks.

See the whole course →