What the same-origin policy is
Same scheme, host and port means same origin. For cross-origin frontend requests the browser decides whether to allow JS to read the response; it blocks reading, not sending.
Simple request vs preflight
- Simple request: GET / some POSTs, no custom headers, sent directly, the
Access-Control-Allow-Originresponse header decides readability; - Preflight: custom headers or non-simple methods trigger an
OPTIONSprobe first; the real request only follows if it passes.
Common misconfigurations
- Blindly returning *: combined with credentials it conflicts and effectively opens to any origin;
- Missing preflight headers: only configuring the main request, so OPTIONS is rejected and the real request never arrives;
- Using a proxy to dodge CORS: works but moves the cross-origin issue server-side, where trust still must be controlled.
How CORS relates to CSRF
These two are routinely conflated although they point in opposite directions:
- CORS protects reading: it decides whether site A may read site B's response, a browser mechanism protecting user data from cross-site reads;
- CSRF attacks writing: the attacker does not need to read the response, only to make the browser send a request with the user's cookie, so permissive CORS is not the cause of CSRF;
- Their defences are therefore different: CORS cannot stop CSRF, you need
SameSite, a CSRF token, orOriginvalidation.
Related headers
Access-Control-Allow-Origincontrols reading only, not whether the request is sent;Access-Control-Max-Agecaches the preflight result; too small and every request pays for an OPTIONS round trip;Vary: Originis essential when echoing the origin dynamically, or a CDN caches one origin's response and serves it to another.
Get these right and you open exactly the cross-origin access you intend without losing the permission boundary.
Debugging cross-origin problems step by step
- Check whether the request was sent: look in the network panel for the preflight. If it fails, the real request never leaves and editing business logic is pointless.
- Read the full response headers: the reason is usually stated there, and reading them beats inferring from the error text.
- Confirm the origin is echoed: with credentials the origin must match exactly and cannot be a wildcard; a wrong echo or a missing declaration fails.
- Reproduce from the command line: send the request with the origin header yourself and read the response, ruling out browser cache and frontend code to isolate server behaviour.
- Separate dev and production config: development often relaxes rules for convenience, make sure production has its own stricter configuration rather than shipping the debug one.
Cross-origin issues are configuration problems, not code bugs; this order narrows the search to one specific header.