OwlTheEngineer
Engineering MistakesEpisode 21 · Backend

CORS: why the browser blocked you

Your server returned 200 OK, Postman works fine, and your frontend console is red. CORS is not a server error; it is a browser security sandbox.

Owl · 2 min read · 0 views
– comments
Ep 21 · Backend
60s VISUAL BREAKDOWN
Episode 21
Watch the fast visual breakdown or discuss on social:

Your API returned 200 OK. Postman received the payload instantly. Curl prints the JSON cleanly. Yet in your browser, the network tab is red and the console shows the classic message:

Access to fetch at 'https://api.example.com/data' from origin 'https://app.example.com' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

The most common misconception about CORS is thinking your server crashed or refused the connection. It didn’t. The browser blocked your JavaScript from reading the response.

What Same-Origin Policy actually protects

Browsers enforce the Same-Origin Policy (SOP). An origin consists of three parts:

https://  api.example.com  :443
───────   ───────────────  ────
Scheme    Host             Port

If any of those three differ between where your frontend is hosted (https://app.example.com) and where your API lives (https://api.example.com), the request is cross-origin.

Without SOP, any malicious website you visit in another tab could run fetch('https://your-bank.com/api/balance'). The browser would automatically attach your session cookies, and the rogue script could read your bank balance and exfiltrate your private data.

CORS is the exception mechanism

CORS (Cross-Origin Resource Sharing) is how a server gives the browser explicit permission to relax SOP for trusted origins.

When your frontend makes a cross-origin request, the browser automatically attaches an Origin header:

GET /data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

If the server wants to allow the frontend to read this response, it must respond with:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com

If that header is missing (or does not match the requesting origin), the browser discards the response and raises a CORS error in the JavaScript console.

Preflight requests (OPTIONS)

For requests that could mutate data or use non-standard headers (like PUT, DELETE, PATCH, or custom headers like Authorization or Content-Type: application/json), the browser sends a preflight check before sending the actual request:

OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization, Content-Type

The server must approve the preflight:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400

Access-Control-Max-Age caches the preflight response so the browser doesn’t have to send an OPTIONS request on every single API call.

The common mistakes

MistakeWhy it hurtsFix
Access-Control-Allow-Origin: * with credentialsBrowsers reject wildcard origins when credentials: 'include' is setReflect the specific allowed origin
Handling CORS in frontend codeFrontend JavaScript cannot bypass browser SOPConfigure headers on the API gateway or backend
Missing OPTIONS route handlerPreflight fails with 404/405 before actual request ever firesReturn 204 No Content on OPTIONS routes
Dropping CORS on error responses (500/404)Hides real server error behind a confusing CORS warningApply CORS middleware globally before error handlers
SYSTEM DESIGN CHECK

Test Your Understanding

Quick architectural scenario challenge based on this breakdown.

SCENARIO

Your React frontend at app.company.com makes a POST request with JSON payload to api.company.com.

Q1.Why does the browser send an HTTP OPTIONS preflight request before a POST request with 'Content-Type: application/json'?

Comments –