← Back to all articles

What CORS Actually Blocks: Same-Origin and Preflight

HTTPSecurityBeginner

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-Origin response header decides readability;
  • Preflight: custom headers or non-simple methods trigger an OPTIONS probe first; the real request only follows if it passes.

Common misconfigurations

  1. Blindly returning *: combined with credentials it conflicts and effectively opens to any origin;
  2. Missing preflight headers: only configuring the main request, so OPTIONS is rejected and the real request never arrives;
  3. 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, or Origin validation.

Related headers

  • Access-Control-Allow-Origin controls reading only, not whether the request is sent;
  • Access-Control-Max-Age caches the preflight result; too small and every request pays for an OPTIONS round trip;
  • Vary: Origin is 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

  1. 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.
  2. Read the full response headers: the reason is usually stated there, and reading them beats inferring from the error text.
  3. 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.
  4. 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.
  5. 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.