Question
Why Browsers Enforce CORS but Postman Does Not in JavaScript
Question
I am sending a POST request from JavaScript to a Flask REST API, but the browser reports this error:
XMLHttpRequest cannot load http://myApiUrl/login.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'null' is therefore not allowed access.
However, the same request works when I send it using Postman.
Why does this happen? Why are XMLHttpRequest, fetch, and AJAX requests in the browser affected by Same-Origin Policy and CORS restrictions, while Postman is not?
Example request code:
$.ajax({
type: 'POST',
dataType: 'text',
url: api,
username: 'user',
password: 'pass',
crossDomain: true,
xhrFields: {
withCredentials: true,
},
})
.done(function (data) {
console.log('done');
})
.fail(function (xhr, textStatus, errorThrown) {
alert(xhr.responseText);
alert(textStatus);
});
I understand that the server must return the correct CORS headers for the browser to allow the request. My question is specifically why the browser blocks it, but Postman does not.
Short Answer
By the end of this page, you will understand why browser-based JavaScript requests are restricted by the Same-Origin Policy, how CORS relaxes those restrictions, and why tools like Postman are not subject to the same browser security model. You will also see how this affects AJAX, fetch, cookies, credentials, and real application design.
Concept
The core idea
A browser is not just an HTTP client. It is a security sandbox that runs untrusted code from websites.
When JavaScript running on one website tries to access a resource from another origin, the browser applies the Same-Origin Policy.
An origin is made of:
- protocol, such as
httporhttps - host, such as
example.com - port, such as
80,443, or5000
If any of these differ, the request is considered cross-origin.
Why browsers restrict cross-origin access
Without these restrictions, any website you visit could silently make authenticated requests to other sites on your behalf and read the responses.
For example, imagine you are logged into:
- your email account
- your bank
- an internal company dashboard
If the browser allowed any page to freely read responses from those services, a malicious site could steal sensitive data just by getting you to visit it.
That is why browsers enforce rules around:
- reading cross-origin responses
- sending credentials like cookies
- exposing response headers to JavaScript
What CORS does
CORS stands for .
Mental Model
Think of the browser as a locked office building.
- Your JavaScript code is a visitor.
- Other websites and APIs are rooms with sensitive documents.
- The Same-Origin Policy is the security desk.
- CORS is the visitor pass issued by the room owner.
If your script wants to enter another room, the browser asks:
- "Do you have permission from that room's owner?"
If the server provides the right CORS headers, the browser lets your code read the response.
Postman is different. It is not a visitor wandering through the office building. It is more like an external courier that can deliver and receive packages directly. It is not governed by the browser's internal room-access rules.
So:
- Browser JavaScript needs a pass.
- Postman does not.
Syntax and Examples
Same-origin vs cross-origin
A request is same-origin only if all three match:
- protocol
- host
- port
Example:
| Page URL | API URL | Same origin? |
|---|---|---|
https://app.example.com | https://app.example.com/api | Yes |
https://app.example.com | https://api.example.com | No |
http://app.example.com | https://app.example.com | No |
http://localhost:3000 | http://localhost:5000 | No |
Step by Step Execution
Example flow
Suppose your page is running at:
http://localhost:3000
And it sends a request to:
http://localhost:5000/login
Code:
fetch('http://localhost:5000/login', {
method: 'POST',
credentials: 'include'
})
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error('Request failed:', error));
Step-by-step
1. The browser compares origins
Page origin:
http://localhost:3000
API origin:
http://localhost:5000
Real World Use Cases
Frontend app calling a backend API
A React, Vue, or plain JavaScript app hosted on one domain often calls an API hosted on another domain or port. Browser CORS rules determine whether the frontend can read those responses.
Local development
A frontend dev server might run on localhost:3000, while an API runs on localhost:5000. Even though both are local, they are still cross-origin because the ports differ.
Login with cookies or sessions
If your app uses session cookies, cross-origin requests become stricter. The browser will not expose credentialed responses unless the server explicitly allows them.
Third-party API access from the browser
Some APIs are designed for server-to-server use only. They may work in Postman but fail in browser JavaScript because they do not allow browser origins.
Internal company systems
A browser must prevent arbitrary websites from reading data from internal dashboards, router admin pages, or local services. Same-Origin Policy helps protect these environments.
Embedded widgets and integrations
A widget loaded on one site may need data from another service. Whether it can access that data depends on the API's CORS policy.
Real Codebase Usage
How developers handle this in real projects
Separate browser concerns from server concerns
Teams usually treat these as different request environments:
- browser client: restricted by CORS
- backend server: not governed by browser Same-Origin Policy
- tools and scripts: usually unrestricted by CORS
Common pattern: backend as a proxy
If a third-party API is not intended for browser use, developers often call it from their own backend instead of directly from frontend JavaScript.
Why?
- avoids exposing secrets in the browser
- avoids browser CORS restrictions for that third-party call
- gives one controlled place for validation and error handling
Validation and guard clauses
Real codebases often check request context early:
if (!userToken) {
throw new Error('Missing token');
}
And on the server side, they validate:
- allowed origins
- allowed methods
- credentials policy
- allowed headers
Credential-aware design
If cookies are involved, developers must coordinate:
- browser request settings such as
credentials: 'include' - server response headers allowing credentials
- cookie settings such as domain, secure, and same-site policies
Common Mistakes
1. Thinking CORS is an HTTP error from the server
Beginners often assume the server rejected the request in a normal application-level way.
In reality, the browser is often the component blocking JavaScript from reading the response.
2. Assuming Postman proves the browser should work
Postman only proves:
- the server endpoint exists
- it can respond to that HTTP request
It does not prove the endpoint is usable from browser JavaScript.
3. Confusing Origin, Host, and URL similarity
These may look similar, but ports and protocols matter.
Broken assumption:
localhost:3000 and localhost:5000 are basically the same place
They are not the same origin.
4. Believing crossDomain: true bypasses CORS
This setting does not disable browser security.
Broken idea:
$.ajax({
url: 'http://localhost:5000/login',
crossDomain: true
});
This merely tells jQuery about the request. The browser still enforces CORS.
5. Using credentials without understanding extra restrictions
Comparisons
Browser JavaScript vs Postman
| Aspect | Browser JavaScript | Postman |
|---|---|---|
| Runs inside a web page sandbox | Yes | No |
| Same-Origin Policy applies | Yes | No |
| CORS checked before exposing response to script | Yes | No |
| Intended to protect users from malicious websites | Yes | Not the same model |
| Can freely read cross-origin responses without server permission | No | Usually yes |
Same-Origin Policy vs CORS
| Concept | Purpose |
|---|---|
| Same-Origin Policy | Default browser restriction that isolates one origin from another |
Cheat Sheet
Quick reference
- Origin = protocol + host + port
- Different protocol, host, or port = cross-origin
- Browsers enforce Same-Origin Policy for page scripts
- CORS is the server's way of saying a browser origin may access a response
- Postman is not a browser page sandbox, so it does not follow browser CORS checks
Common facts
- Postman working does not mean browser JavaScript will work
localhost:3000andlocalhost:5000are different originscrossDomain: truedoes not bypass CORSwithCredentials: trueorcredentials: 'include'adds stricter requirementsOrigin: nulloften appears when loading fromfile://
Typical browser flow
- JavaScript sends cross-origin request
- Browser checks Same-Origin Policy
- Server returns response
- Browser exposes or blocks response based on CORS headers
Important headers
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Rule of thumb
If the request comes from:
FAQ
Why does Postman work but my browser AJAX request fails?
Postman is not running inside a browser page sandbox, so Same-Origin Policy and CORS checks do not apply in the same way.
Is CORS a client-side or server-side feature?
It is both in practice: the server sends CORS headers, and the browser enforces them.
Does CORS stop the request from being sent?
Not always. Sometimes the request is sent and the browser blocks JavaScript from reading the response. In other cases, the browser may send a preflight first.
Why is localhost on another port considered cross-origin?
Because origin includes the port. http://localhost:3000 and http://localhost:5000 are different origins.
Why does the error say Origin 'null'?
This often happens when the page is loaded from file:// or another context without a normal web origin.
Does withCredentials: true fix CORS?
No. It only tells the browser to include credentials when allowed. The server must still explicitly permit credentialed cross-origin access.
Why can I open the API URL directly in the browser but not call it with fetch?
Opening a URL directly is a page navigation, which has a different security model than letting JavaScript read a cross-origin response.
Can frontend code bypass CORS on its own?
Mini Project
Description
Build a small example that demonstrates the difference between a same-origin request and a cross-origin browser request. The goal is not to fix CORS, but to observe when the browser enforces it and understand why API tools behave differently.
Goal
Create a page that makes one same-origin request and one cross-origin request, then inspect the browser behaviour and explain the difference.
Requirements
- Create a simple JavaScript page that sends an HTTP request to its own origin.
- Add a second request to a different origin or port.
- Log success or failure for both requests.
- Open the browser Network tab and inspect what happened.
- Write one short note explaining why Postman may still succeed for the second request.
Keep learning
Related questions
Deep Cloning Objects in JavaScript: Methods, Trade-offs, and Best Practices
Learn how to deep clone objects in JavaScript, compare structuredClone, JSON methods, and recursive approaches with examples.
Get Screen, Page, and Browser Window Size in JavaScript
Learn how to get screen size, viewport size, page size, and scroll position in JavaScript across major browsers with clear examples.
How JavaScript Closures Work: A Beginner-Friendly Guide
Learn how JavaScript closures work with simple explanations, examples, common mistakes, and practical use cases for real code.