DEV Community

Cover image for CORS Explained Simply: Why Browsers Block Your API Requests ?
Arpit Jana
Arpit Jana

Posted on

CORS Explained Simply: Why Browsers Block Your API Requests ?

Every developer eventually encounters the infamous error:

Access to fetch at 'https://api.example.com' from origin 'https://myapp.com'
has been blocked by CORS policy
Enter fullscreen mode Exit fullscreen mode

At first glance, it feels like the server blocked your request.

But here’s the twist:

The request was not blocked.
The browser simply refused to let your JavaScript read the response.

Understanding this single idea makes CORS far less mysterious.

Let's break it down step by step.


🟦 1. What is CORS?

CORS (Cross-Origin Resource Sharing) is a browser-enforced security rule.

It controls whether JavaScript running in the browser is allowed to read a response coming from another origin.

Important truths:

  • 🚨 CORS is not a server security system
  • 🚨 CORS does not block network requests
  • 🚨 CORS is enforced only by browsers

Tools like Postman, curl, or server-side code are not restricted by CORS.


🟦 2. What is an Origin?

An Origin is defined by three components together:

Origin = Protocol + Host + Port
Enter fullscreen mode Exit fullscreen mode

Examples:

URL Origin
https://frontend.com https://frontend.com:443
http://frontend.com ❌ different origin (protocol)
https://frontend.com:3000 ❌ different origin (port)
https://api.frontend.com ❌ different origin (subdomain)

πŸ‘‰ Even a small change creates a different origin.


🟦 3. Same-Origin Policy (The Default Rule)

Browsers enforce something called the Same-Origin Policy.

It states:

JavaScript can only read responses from the same origin by default.

Example:

Frontend β†’ https://frontend.com
Backend  β†’ https://api.com
Enter fullscreen mode Exit fullscreen mode

Result:

❌ JavaScript cannot read the response
Enter fullscreen mode Exit fullscreen mode

Unless the server explicitly allows it using CORS headers.


🟦 4. The Origin Request Header

When a browser sends a cross-origin request, it automatically attaches this header:

Origin: https://frontend.com
Enter fullscreen mode Exit fullscreen mode

Key details:

  • Added automatically by the browser
  • Cannot be modified by JavaScript
  • Tells the server where the request came from

It basically means:

β€œThis request originated from https://frontend.com”

Again, remember:

Postman and curl do not enforce CORS, which is why APIs often work there but fail in the browser.


🟦 5. The Access-Control-Allow-Origin Response Header

The server decides whether the browser should allow access.

Example response header:

Access-Control-Allow-Origin: https://frontend.com
Enter fullscreen mode Exit fullscreen mode

Meaning:

β€œBrowser, it's safe to expose this response to https://frontend.com”

Common cases:

🟒 Allow a specific origin

Access-Control-Allow-Origin: https://frontend.com
Enter fullscreen mode Exit fullscreen mode

🟒 Allow all origins

Access-Control-Allow-Origin: *
Enter fullscreen mode Exit fullscreen mode

This is common for public APIs.

However:

* cannot be used with cookies or credentials
Enter fullscreen mode Exit fullscreen mode

🎬 The Real CORS Flow (The Part Most Developers Miss)

Let's walk through what actually happens.

▢️ Setup

Frontend: https://myapp.com
Backend : https://api.mybackend.com
Enter fullscreen mode Exit fullscreen mode

User is logged in and has a session cookie.


▢️ Step 1 β€” JavaScript Sends a Request

fetch("https://api.mybackend.com/profile", {
  credentials: "include"
});
Enter fullscreen mode Exit fullscreen mode

This tells the browser:

This is a cross-origin request and I want to include cookies.

Without credentials: "include":

❌ Cookies are not sent
Enter fullscreen mode Exit fullscreen mode

▢️ Step 2 β€” Browser Checks if a Preflight is Needed

Browsers classify requests into simple and complex.

🟒 Simple requests (no preflight)

  • Methods: GET, POST, HEAD
  • Safe headers
  • Standard content types

🟠 Complex requests (require preflight)

  • PUT / DELETE / PATCH
  • Custom headers
  • Authorization headers

If a request is complex, the browser performs a preflight check first.


▢️ Step 3 β€” The Preflight Request

The browser sends an OPTIONS request automatically.

OPTIONS /profile HTTP/1.1
Host: api.mybackend.com
Origin: https://myapp.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Enter fullscreen mode Exit fullscreen mode

This is basically the browser asking:

"Hey server, is it okay if myapp.com sends this request?"

No cookies are sent yet.


▢️ Step 4 β€” Server Responds to Preflight

The server must respond with permission headers:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 86400
Enter fullscreen mode Exit fullscreen mode

Meaning:

Header Meaning
Allow-Origin which origin is allowed
Allow-Credentials cookies allowed
Allow-Methods permitted HTTP methods
Allow-Headers permitted request headers
Max-Age how long the permission is cached

🚨 Critical rule

If using cookies:

Access-Control-Allow-Origin cannot be "*"
Enter fullscreen mode Exit fullscreen mode

▢️ Step 5 β€” The Actual Request Happens

After preflight approval, the browser sends the real request.

GET /profile
Origin: https://myapp.com
Cookie: sessionId=abc123
Authorization: Bearer token
Enter fullscreen mode Exit fullscreen mode

Now the server processes the request normally.


▢️ Step 6 β€” Server Sends Response

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

Response body:

{ "name": "John", "email": "john@example.com" }
Enter fullscreen mode Exit fullscreen mode

▢️ Step 7 β€” Browser Performs the Final Security Check

The browser verifies:

  • credentials: "include" used
  • Access-Control-Allow-Credentials: true
  • specific Access-Control-Allow-Origin
  • cookies allow cross-site usage

If everything passes:

βœ… JavaScript receives the response
Enter fullscreen mode Exit fullscreen mode

If not:

❌ Browser blocks JavaScript access
Enter fullscreen mode Exit fullscreen mode

πŸŸ₯ The Most Misunderstood Truth About CORS

Many developers believe:

CORS blocks requests from reaching the server.

But the reality is different.

Server  β†’ response sent
Network β†’ response delivered
Browser β†’ response received
JS      β†’ access blocked
Enter fullscreen mode Exit fullscreen mode

The browser simply places the response behind a security curtain.


🟦 Proof: Check DevTools

Open Network tab in your browser.

Often you'll see:

Status: 200 OK
Response body present
Enter fullscreen mode Exit fullscreen mode

Yet your code throws:

TypeError: Failed to fetch
Enter fullscreen mode Exit fullscreen mode

Because:

The browser blocked JavaScript from reading the response.


🟦 Side Effects Still Happen

Even if CORS blocks the response:

  • cookies were sent
  • server logic executed
  • database changes happened

Example:

POST /transfer-money
Enter fullscreen mode Exit fullscreen mode

Possible result:

βœ… money transferred
❌ JavaScript cannot read the response
Enter fullscreen mode Exit fullscreen mode

This is why CSRF protection exists.

Important distinction:

CORS != CSRF protection
Enter fullscreen mode Exit fullscreen mode

🟦 Why CORS Exists

Without CORS:

  • Any website could call your APIs
  • Cookies would attach automatically
  • Sensitive user data could be stolen

CORS protects:

βœ… users
βœ… browser environment
Enter fullscreen mode Exit fullscreen mode

But not:

❌ servers
❌ databases
Enter fullscreen mode Exit fullscreen mode

πŸŸͺ The Mental Model That Makes CORS Easy

Component Role
Origin header browser introducing itself
Allow-Origin server's guest list
Browser security bouncer
JavaScript requesting access

Or remember this line:

CORS decides who can READ, not who can SEND.


🟒 Final One-Line Summary

CORS is a browser security rule that determines whether JavaScript is allowed to read cross-origin responses, even though the request and response already happened successfully.


🧩 Cover Image Icons Credits

Top comments (0)