FSD Frontend System Design

Part II β€” The Shield Β· Lesson 3.14

Cross-Origin Resource Sharing (CORS)

In one lineThe browser blocks cross-origin reads by default; CORS is the server's way of granting an exception.

Where you've seen itapp.example.com fetching from api.example.com. Same company, same certificate, still a different origin.

  • Time17 min
  • DiagramPreflight OPTIONS sequence + origin comparison
  • Chapter3 Β· Security

Diagram

diagram
WHAT COUNTS AS THE SAME ORIGIN β€” all three must match

  https://app.example.com:443/users
  β””β”€β”¬β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”¬β”˜
  scheme      host         port

  vs https://app.example.com/orders      βœ“ same origin
  vs http://app.example.com              βœ— scheme differs
  vs https://api.example.com             βœ— host differs
  vs https://app.example.com:8443        βœ— port differs
diagram
PREFLIGHT β€” asked before anything that isn't "simple"

  Browser                              Server
    │── OPTIONS /orders ──────────────►│
    β”‚   Origin: https://app.example.comβ”‚
    β”‚   Access-Control-Request-Method: DELETE
    β”‚   Access-Control-Request-Headers: authorization
    β”‚                                  β”‚
    │◄─ 204 ───────────────────────────│
    β”‚   Access-Control-Allow-Origin: https://app.example.com
    β”‚   Access-Control-Allow-Methods: GET, POST, DELETE
    β”‚   Access-Control-Allow-Headers: authorization
    β”‚   Access-Control-Max-Age: 86400   ← cache the preflight
    β”‚                                  β”‚
    │── DELETE /orders/9 ─────────────►│  the real request, now allowed
diagram
SIMPLE REQUEST β€” no preflight

  method   GET Β· HEAD Β· POST
  headers  only Accept, Accept-Language, Content-Language, Content-Type
  type     Content-Type ∈ {text/plain, multipart/form-data,
                           application/x-www-form-urlencoded}

  application/json β‡’ NOT simple β‡’ preflight. This is why almost
  every real API call triggers an OPTIONS first.

How it works

  1. The browser attaches Origin to cross-origin requests.
  2. For non-simple requests it sends an OPTIONS preflight first and waits for permission.
  3. The server responds with Access-Control-Allow-* headers; the browser enforces them.
  4. The request usually still reaches the server β€” CORS blocks the response from being read by JS. It is not server-side access control.
  5. For cookies, the client sets credentials: 'include' and the server must send Access-Control-Allow-Credentials: true with an explicit origin β€” * is rejected in that combination.

Trade-offs

SettingConsequence
Allow-Origin: *Public read-only APIs only; no credentials
Echoing the Origin header backEquivalent to * unless you validate against an allowlist
Max-Age: 86400Removes a preflight round trip per session
Same-origin via reverse proxyNo CORS at all β€” often the simplest fix

Interview angle

"You get a CORS error. Is it a frontend bug?"

  • No β€” CORS is granted by the server, so the fix is a response header, not client code.
  • Read the exact message: disallowed origin, disallowed header, or missing credentials support each need a different header.
  • The clean production answer is often to remove the cross-origin call entirely by proxying the API under the same origin.

Recap

  • Same origin = scheme + host + port, all three.
  • Non-simple requests (including any JSON body) pay a preflight; cache it with Max-Age.
  • CORS protects the user's data from other sites β€” it never protects your API from a direct client.