Question

Why Browsers Enforce CORS but Postman Does Not in JavaScript

javascriptjquerycorspostmansame-origin-policy

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 http or https
  • host, such as example.com
  • port, such as 80, 443, or 5000

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.
Take Quiz

Syntax and Examples

Same-origin vs cross-origin

A request is same-origin only if all three match:

  • protocol
  • host
  • port

Example:

Page URLAPI URLSame origin?
https://app.example.comhttps://app.example.com/apiYes
https://app.example.comhttps://api.example.comNo
http://app.example.comhttps://app.example.comNo
http://localhost:3000http://localhost:5000No

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: true tells jQuery this is a cross-origin request.
  • withCredentials: true means 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 OPTIONS preflight 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.

Take Quiz

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

AspectBrowser JavaScriptPostman
Runs inside a web page sandboxYesNo
Same-Origin Policy appliesYesNo
CORS checked before exposing response to scriptYesNo
Intended to protect users from malicious websitesYesNot the same model
Can freely read cross-origin responses without server permissionNoUsually yes

Same-Origin Policy vs CORS

ConceptPurpose
Same-Origin PolicyDefault browser restriction that isolates one origin from another
CORSControlled way for a server to relax that restriction for specific origins

Browser request vs server-to-server request

AspectBrowser requestServer-to-server request
Triggered by frontend codeYesNo
Browser security rules involvedYesNo
Often affected by CORSYesNo
Good for private API keysNoYes

fetch/XHR vs visiting a URL directly

ActionWhat happens
JavaScript fetch or XHRBrowser decides whether script may read response
Typing URL in address barBrowser 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.

Take Quiz

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:3000 and localhost:5000 are different origins
  • crossDomain: true does not bypass CORS
  • withCredentials: true or credentials: 'include' adds stricter requirements
  • Origin: null often appears when loading from file://

Typical browser flow

  1. JavaScript sends cross-origin request
  2. Browser checks Same-Origin Policy
  3. Server returns response
  4. 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.

Take Quiz

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.
Take Quiz

Keep learning

Related questions

View more