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
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
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
Result:
β JavaScript cannot read the response
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
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
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
π’ Allow all origins
Access-Control-Allow-Origin: *
This is common for public APIs.
However:
* cannot be used with cookies or credentials
π¬ 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
User is logged in and has a session cookie.
βΆοΈ Step 1 β JavaScript Sends a Request
fetch("https://api.mybackend.com/profile", {
credentials: "include"
});
This tells the browser:
This is a cross-origin request and I want to include cookies.
Without credentials: "include":
β Cookies are not sent
βΆοΈ 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
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
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 "*"
βΆοΈ 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
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
Response body:
{ "name": "John", "email": "john@example.com" }
βΆοΈ 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
If not:
β Browser blocks JavaScript access
π₯ 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
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
Yet your code throws:
TypeError: Failed to fetch
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
Possible result:
β
money transferred
β JavaScript cannot read the response
This is why CSRF protection exists.
Important distinction:
CORS != CSRF protection
π¦ 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
But not:
β servers
β databases
πͺ 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)