Skip to content

Cross-Origin Resource Sharing (CORS)

CORS is a browser-enforced security mechanism that restricts how JavaScript running on one origin can read responses from another origin.

Key points:

  • CORS is enforced only by browsers
  • CORS does not block requests from being sent
  • CORS controls response visibility, not server execution

If you see a CORS error, the server was still reached.

Origin vs Same-Origin Policy (SOP)

What Is an Origin?

An origin is defined as:

scheme + host + port

Examples:

  • https://example.comhttp://example.com
  • https://example.comhttps://api.example.com
  • https://example.com:443https://example.com:8443

Same-Origin Policy (SOP)

The Same-Origin Policy states:

JavaScript may freely read responses only from the same origin.

Everything else is blocked by default.

CORS exists as a controlled relaxation of SOP.

What CORS Actually Protects

CapabilityAllowed cross-origin?
Sending requestsYes
Submitting formsYes
Loading images/scriptsYes
Reading response bodyNo (unless CORS allows it)
Reading headersNo (unless exposed)

Critical insight
CORS protects data confidentiality, not server integrity.

Browser Mental Model

  1. JavaScript on frontend.com calls api.backend.com
  2. Browser sends the HTTP request
  3. Server processes the request
  4. Browser inspects response headers
  5. Browser either:
    • Exposes the response to JavaScript
    • Or blocks it with a CORS error

Simple Requests vs Preflighted Requests

Simple Requests (No Preflight)

A request is simple if all are true:

  • Method is GET, POST, or HEAD
  • Headers are simple (Accept, Content-Type, etc.)
  • Content-Type is one of:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Result:

  • Request is sent immediately
  • No OPTIONS preflight

Preflighted Requests

Anything else triggers a preflight, for example:

  • PUT, PATCH, DELETE
  • Custom headers (Authorization, X-*)
  • application/json

Preflight in Detail

Preflight Request

OPTIONS /resource
Origin: https://frontend.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization

Required Preflight Response

Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Authorization

If the response is missing or mismatched:

  • Browser blocks JavaScript access
  • Actual request may never be sent

Credentials and CORS (Most Common Pitfall)

Credentialed Requests

Credentials include:

  • Cookies
  • HTTP authentication
  • Client TLS certificates

To allow credentials:

Access-Control-Allow-Credentials: true

Rules:

  • Access-Control-Allow-Origin must be explicit
  • * is invalid when credentials are used

Why Wildcard Origins Break

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true # INVALID

Browser behavior:

  • Always blocked

FastAPI CORS Configuration

from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

Common Failure Modes

SymptomRoot Cause
Works in curl, fails in browserCORS is browser-only
GET works, POST failsPreflight rejected
Cookies not sentCredentials not enabled
Auth header blockedHeader not allowed
Intermittent failuresOrigin mismatch

CORS vs CSRF

AspectCORSCSRF
ProtectsResponse dataUser intent
ThreatData exfiltrationState change
Blocks requestNoNo
Browser enforcedYesPartially
Server enforcedNoYes

Why APIs Work Outside Browsers

Tools that ignore CORS:

  • curl
  • Postman
  • Mobile apps
  • Server-to-server calls

CORS exists only to protect browsers from malicious JavaScript.

Interview Traps

  • “CORS prevents CSRF” → false
  • “CORS blocks the request” → false
  • “Disable CORS in production” → dangerous
  • “CORS is a backend security feature” → incorrect

Rules of Thumb

  • If JS cannot read the response → CORS
  • If backend still sees traffic → expected
  • If cookies involved → explicit origin + credentials
  • If JSON + auth → expect preflight
  • Never combine * with credentials