Have you ever seen a message in your console like: "Access to fetch at '...' from origin '...' has been blocked by CORS policy"? CORS doesn't draw attention to itself when everything is working, but at a crucial moment, it firmly blocks unauthorized actions. For example, reading the response to a cross-origin request without the server's permission.
Web technologies allow us to perform banking transactions instantly, make payments in online stores, collect and process data - but the more actively websites communicate with each other, the more pressing the issue of security becomes.
CORS is a protection mechanism for cross-origin requests, but the first time I encountered this error, I didn't understand how to fix it. I tried adding the required header mentioned in the error message (we'll look at where to find these errors later), but that didn't help. I had to dig deeper: to understand what an origin is, how a simple request differs from a preflight request, why using a wildcard (*) doesn't work with credentials, and where CORS ends and CSRF begins (don't worry too much about these terms now, they will be explained throughout the article).
In this article, I will briefly answer questions about why the CORS policy was created, how it works, why a simple action like "setting a header on the backend" might not be enough, and what secure patterns to choose for the frontend.
To understand the logic behind CORS, we need to figure out where this security policy started - namely, with the Same-Origin Policy (SOP): what it allows, what it forbids, and why CORS wouldn't be needed without it.
What is SOP and Origin
It all started back in 1995 when everyone's beloved (or not so beloved) JavaScript appeared and its use was implemented on web pages. At that moment, the concept of a browser policy emerged, and it was called the Same-Origin Policy (SOP).
SOP is a fundamental principle of browser security, guaranteeing that scripts from one Origin cannot access data from another origin without explicit permission. Initially, SOP only protected access to the DOM (page structure) of other origins, but it was later extended to other sensitive objects (like cookies and global JS objects).
So, what is an Origin? An Origin (hereinafter referred to as Source) is a unique combination of scheme (protocol), domain, and port (see the diagram below). If at least one of these components differs, one Source will be different from another.

Examples of Matching or Non-Matching Origins
The comparison table below provides examples:

How SOP Works in Practice

Let's now break down a scenario step-by-step where the SOP policy might be triggered (Image above):
- A user visits and requests resources located at
https://www.a.com.
- The loaded resources from
https://www.a.com in the user's browser
then initiate a request for resources located at
https://www.b.com.
- The browser checks the so-called Origin and, as seen in this case,
the origins do not match, so the browser blocks access to the
resources from https://www.b.com.
What Would Happen Without SOP
Why was SOP necessary in the first place? What's the benefit for me as a user? And if I'm a web developer, why should I keep this in mind and work around the restrictions?
It's hard to argue with the primary reason: security.

Let's imagine SOP doesn't exist and play out a scenario (image above), disastrous for both the user (who becomes vulnerable) and the developer (whose product loses trust):
- The user logs into their bank at
bank.com (a session cookie is
saved in the browser).
- They then visit a malicious website,
bad-site.com, which contains
a hidden malicious script.
- This script initiates a request with the session cookies to
bank.com on the user's behalf.
- As a result, the malicious script receives a response from the bank
without your knowledge!
Without SOP, this script could get the user's recent transaction list, create a new transaction, etc. This is because, according to the original concept of the World Wide Web, browsers are obliged to add authentication data like session cookies and authorization headers at the platform level when making requests to the bank's site, based on that site's domain.
Let's return to our reality, where a protection mechanism against such nuisances exists (image below). Steps 1-3 from the disastrous scenario would be the same, but at step 4, SOP would block access to the requested resources.

It was precisely to prevent such attacks that the Same-Origin Policy was introduced: browsers started automatically blocking scripts from one Origin from accessing data from another.
Important: Although JavaScript indeed doesn't have direct access to
the bank session cookies, it can still send requests to the bank's
website using those bank session cookies, as in the situations on
images above.
Experienced readers might say you can simply set the HttpOnly flag on cookies. However, this flag only became a standard in 2002. Others might mention SameSite. But it only appeared in 2016 and became a standard only in 2019-2020.
SOP restricts reading data from a foreign origin, but it does not block the sending of requests to foreign domains. The browser would still automatically include cookies for bank.com when submitting a form to bank.com - it's just that the script from bad-site.com wouldn't find out what the bank returned. SOP only prevents the attacker from reading the response and confirming the attack worked. To protect against such scenarios on the server side, additional measures are needed (e.g., CSRF tokens in forms, setting appropriate SameSite and HttpOnly values, etc.).
This is the Same-Origin Policy (SOP) in the browser, designed to protect the user. It doesn't seem too complicated overall, right? The user doesn't need to think about it because the browser developers have already thought about it and implemented a protection mechanism. The developer also doesn't need to worry about it specifically in their code - it's enough to follow the established rules.
Restrictions Imposed by SOP
So, SOP imposes a number of strict restrictions on interaction between resources from different Origins:
- Blocking Access to Page Content. A script cannot read or modify the content of a page from another domain. For example, JavaScript
from site-a.com cannot access the DOM, cookies, localStorage, or
other data of a page on site-b.com.
- Frame Isolation. If an
<iframe src="..."> from a foreign domain is embedded in a page, the parent script cannot access
iframe.contentWindow.document of that frame (and vice versa) access
will be denied as long as their origins differ.
- Blocking Access to HTTP Responses via XHR/Fetch. The browser blocks receiving the response to AJAX requests sent by a script to
another domain. That is, you can send a fetch to a third-party API,
but if their Origins are different, the browser will not provide the
response to the script.
- Exceptions (Allowed Loads). SOP restricts access to data from third-party origins, not the loading of resources themselves. The
browser can load images, styles, scripts, media, and frames from
another domain without errors and use them as-is: display an image,
apply a style, execute a script, play a video, render an iframe.
However, the page's JavaScript is not allowed to read the internal
content of these resources (image pixels, style rules, frame DOM,
video bytes, etc.) unless the resource's server explicitly permits
access via CORS.
Essentially, SOP says: "you can't read others' data," and this provides basic isolation. But the real web has long been multi-domain: we pull fonts from CDNs, call external APIs, facilitate communication between microservices. How to make such exchanges legitimate and secure without breaking isolation? This is where CORS comes onto the stage - a set of rules for coordinated access between different origins.
Author: Bair Ochirov