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 Cross-Origin Resource Sharing.
It is a browser-based mechanism that lets a server explicitly say:
- which origins may access it
- which methods are allowed
- whether credentials are allowed
- which headers may be sent or exposed
So CORS is not something that Postman needs to obey. It is something the browser checks before letting JavaScript access the response.
Why Postman works
Postman is not running your code inside a normal web page sandbox. It behaves like a general-purpose API client.
That means:
- it can send HTTP requests directly
- it is not enforcing browser Same-Origin Policy for web page scripts
- it does not need the server to grant permission through CORS before showing you the response
So the difference is not that the server responded differently in some magical way. The difference is that:
- browser JavaScript is restricted for security reasons
- Postman is not a browser page executing untrusted site code
Important distinction
CORS does not usually stop the HTTP request from being sent. Instead, it stops the browser from making the response available to the page's JavaScript unless the server allows it.
In some cases, the browser first sends a preflight request using OPTIONS to ask the server whether the real request is allowed.
Why Origin 'null' may appear
The error mentions Origin 'null'. That often happens when the page was loaded from places such as:
- a local file using
file:// - a sandboxed document
- some special browser contexts
From the browser's point of view, that is still an origin situation it must protect, so it may send Origin: null and require the server to allow it explicitly.
Why this matters in real programming
If you build frontend and backend systems separately, this affects:
- local development on different ports
- frontend apps calling APIs on another domain
- authenticated requests with cookies or sessions
- embedded widgets and third-party integrations
Understanding that CORS is a browser security rule helps you diagnose these situations correctly. The key question becomes:
Is this request being made by browser JavaScript, or by a non-browser client?
That single distinction explains why Postman works while browser AJAX fails.
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 |
Browser AJAX example
fetch('http://localhost:5000/login', {
method: 'POST',
credentials: 'include'
})
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error(error));
If this is cross-origin, the browser checks whether the server allows it through CORS.
Typical CORS response headers
A server that wants to allow a browser origin may send headers like:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
These headers tell the browser that JavaScript from that origin may access the response.
Postman example
Postman can send the same HTTP request:
POST /login HTTP/1.1
Host: localhost:5000
Authorization: Basic ...
But Postman does not apply browser Same-Origin Policy when deciding whether to show you the response.
Your jQuery example
$.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);
});
What this means
crossDomain: truetells jQuery this is a cross-origin request.withCredentials: truemeans cookies or credentials may be included.- For credentialed cross-origin requests, the server must allow that explicitly.
Even though the request syntax is valid, the browser still decides whether your page is allowed to access the response.
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
The ports differ, so this is cross-origin.
2. The browser applies security rules
Because the request comes from page JavaScript, the browser checks CORS rules.
3. The browser may send the request or preflight first
Depending on the method, headers, and credentials, the browser may:
- send the request directly, or
- send an
OPTIONSpreflight request first
4. The server responds
If the server response does not include the required CORS headers, the browser treats the response as blocked for JavaScript.
5. JavaScript cannot access the response
Your .then(...) or .done(...) handler may not get usable data, and the browser logs a CORS error.
6. Postman does not do this check
If you send the same request in Postman:
- Postman sends the HTTP request
- the server responds
- Postman shows the response
There is no browser page sandbox involved, so there is no Same-Origin Policy check in that context.
Trace with simple pseudo-sequence
Browser page script -> Browser security layer -> API server
|
v
Check Same-Origin Policy
|
If allowed by CORS, expose response
If not allowed, block page access
That middle security layer is the reason browser code behaves differently from Postman.
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
Error handling patterns
CORS errors in the browser often appear as network-like failures. Real apps therefore:
- log request context
- inspect browser devtools network tab
- separate true server errors from browser-blocked responses
Environment configuration
Production apps often configure allowed origins by environment:
- local development origin
- staging origin
- production frontend origin
This avoids accidentally allowing every origin while still supporting real deployment workflows.
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
With cookies or authenticated requests, the rules are stricter.
Broken expectation:
fetch(url, {
credentials: 'include'
});
This does not force the browser to allow the response. The server must explicitly permit credentialed cross-origin access.
6. Ignoring Origin: null
If your page is loaded from file://, the browser may use Origin: null.
That can cause confusing results in local testing.
7. Trying to solve a browser-security problem only on the client
You cannot fix a CORS permission problem just by changing frontend JavaScript if the server does not allow the origin.
8. Assuming the request never happened
Sometimes the browser did send the request, but your script is not allowed to read the response.
Check the browser network tab instead of relying only on console errors.
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 |
| CORS | Controlled way for a server to relax that restriction for specific origins |
Browser request vs server-to-server request
| Aspect | Browser request | Server-to-server request |
|---|---|---|
| Triggered by frontend code | Yes | No |
| Browser security rules involved | Yes | No |
| Often affected by CORS | Yes | No |
| Good for private API keys | No | Yes |
fetch/XHR vs visiting a URL directly
| Action | What happens |
|---|---|
JavaScript fetch or XHR | Browser decides whether script may read response |
| Typing URL in address bar | Browser navigates to the page; this is a different security context |
This is why an endpoint may open in the browser or work in Postman but still fail when called through AJAX.
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:
- browser JavaScript -> CORS matters
- Postman, curl, backend code -> browser CORS does not apply
Debug tip
Use the browser's Network tab to check:
- whether the request was sent
- whether a preflight happened
- which response headers were returned
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?
No. If the server does not allow the origin, browser JavaScript cannot override that policy.
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
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Access the Correct this Inside a JavaScript Callback
Learn why JavaScript this changes in callbacks and how to preserve an object context using bind, arrow functions, and event handler patterns.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.