Topic 7.6
CORS, HSTS & Browser Security Headers
In one line
Browsers enforce the same-origin policy: a page from one origin (scheme + host + port) can't read responses from another unless that server allows it with CORS headers like Access-Control-Allow-Origin. Some requests first send an OPTIONS 'preflight'. CORS is enforced by the browser, not the server, so it protects users, not APIs. Other response headers harden the browser too: HSTS forces HTTPS, CSP limits where scripts load from, and X-Content-Type-Options, frame-ancestors, and Referrer-Policy close common gaps.
Think of it like this
A school that only lets a parent collect a child if the child's own class teacher has written that parent's name on an approved list. The rule is enforced at the school gate (the browser), not by the parent (the server being asked).
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Origin
- The scheme, host, and port of a URL, the unit browsers use for isolation.
- Same-origin policy
- The browser rule that scripts can only read responses from their own origin by default.
- CORS
- Cross-Origin Resource Sharing: headers that let a server allow specific other origins to read its responses.
- Preflight
- An OPTIONS request the browser sends first to ask whether a cross-origin request is allowed.
- HSTS
- HTTP Strict Transport Security: tells browsers to use only HTTPS for a site for a set time.
- CSP
- Content Security Policy: a header restricting where a page may load scripts and other resources from.
Step by step
01The console error
Tiffin moves its API to https://api.tiffin.in, while the website stays at https://tiffin.in. The menu loads in curl, but in the browser Meera sees an empty page and the console shows a CORS error.
Access to fetch at 'https://api.tiffin.in/menu' from origin 'https://tiffin.in'
has been blocked by CORS policy: Response to preflight request doesn't pass
access control check: No 'Access-Control-Allow-Origin' header is present on
the requested resource.02Testing the preflight by hand
Arjun simulates the browser's preflight with curl, sending the same headers. Before the fix, the API returns no CORS headers. After adding an allow-list, the preflight answers correctly.
const ALLOWED = new Set(["https://tiffin.in", "https://admin.tiffin.in"]);
export function cors(req, res, next) {
const origin = req.headers.origin;
if (ALLOWED.has(origin)) { // allow-list, never "reflect anything"
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Vary", "Origin"); // caches must keep per-origin copies
res.setHeader("Access-Control-Allow-Credentials", "true");
}
if (req.method === "OPTIONS") {
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
res.setHeader("Access-Control-Max-Age", "600"); // browser caches the preflight 10 min
return res.status(204).end();
}
next();
}03Hardening headers at the edge
Priya adds security headers in Nginx so every response gets them. HSTS starts with a short max-age and grows once everything works over HTTPS. The CSP allows scripts only from Tiffin itself and the payment provider's checkout script.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://checkout.razorpay.com; frame-ancestors 'none'; base-uri 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Reflecting any Origin with credentials
To silence CORS errors quickly, the API echoes back whatever Origin header arrives and sets Allow-Credentials: true.
Myth vs fact
Myth
CORS protects my API from other servers.
Fact
Only browsers enforce CORS. Scripts, curl, and servers ignore it. Protect APIs with authentication and authorization.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Roll out CSP with
Content-Security-Policy-Report-Onlyfirst, collecting violation reports, then switch to enforcing. Before submitting a domain to the HSTS preload list, be sure every subdomain supports HTTPS, because removal takes months.
Remember this
- 1
An origin is scheme + host + port:
https://tiffin.inandhttps://api.tiffin.inare different origins, and so arehttp://vshttps://and:3000vs:443. - 2
By default, JavaScript on
https://tiffin.incan *send* a request tohttps://api.tiffin.inbut can't *read* the response unless the API replies withAccess-Control-Allow-Origin: https://tiffin.in. - 3
Preflight: for non-simple requests (methods like PUT/DELETE, JSON content type, custom headers like Authorization), the browser first sends
OPTIONSwithAccess-Control-Request-Method/Headers. The server must answer with matchingAllow-*headers, or the real request is never sent. - 4
Credentials (cookies) need
Access-Control-Allow-Credentials: true*and* a specific origin.*isn't allowed with credentials. Never reflect any incoming Origin back blindly. - 5
CORS errors appear only in the browser console. curl ignores CORS completely, which is why 'it works with curl' doesn't mean the browser will work, and why CORS isn't access control for the API.
- 6
Hardening headers:
Strict-Transport-Security(HSTS: always use HTTPS),Content-Security-Policy(allowed script, style, and frame sources),X-Content-Type-Options: nosniff,frame-ancestorsin CSP (clickjacking protection),Referrer-Policy.
Explain it without notes
Why does the API work in curl but fail in the browser?
What does HSTS protect against?
Practice
Check which security headers a site sends.
Write the CORS headers for an API used by https://tiffin.in with cookies.
Trade-offs
- ↔
Strict CSP and HSTS raise security but can break third-party widgets or HTTP-only subdomains, so test in report-only mode first. Long preflight caching reduces extra requests but delays CORS changes.
Done when you can
I can explain origins and the same-origin policy.
I configure CORS with an allow-list and handle preflights.
I set HSTS, CSP, and nosniff headers.