TL;DR The Same Origin Policy (SOP) enforces that websites from different origins cannot access each other's content.
A good explanaition of the SOP by Eric Portis can be found here; in short:
The concept of origins is the fundamental security/privacy boundary of the web.
Two sites or ressources that share an origin can interact with each other however they like.
Two sites that have a different origin can still interact with each other in certain ways (writing cross-origin using POST forms, embedding cross-origin ressources using iframes or image tags), but what they cannot do is read cross-origin (unless specially-granted permission via CORS, see below).
Why is reading not allowed? Because everyone sees a different WWW (e.g. when you're logged in into Facebook) "and a website shouldn’t be able to see the rest of the web through its visitors’ eyes".
How is the origin defined?
The origin is defined as the triplet of...
protocol (e.g. "http" or "https"),
hostname (including subdomain, i.e. "example.com" and "www.example.com" are considered different origins) and
port (usually 80 for HTTP or 443 for HTTPS).
SOP does *not* apply to scripts loaded via the <script> tag, they're expected to be static:
SOP *does* apply to iframes (with very few exceptions)...:
Exceptions to the SOP:contentWindow.length, postMessage(), contentWindow.name (see by clicking the button above)
...unless the iframe has the same origin:
SOP also applies to XMLHttpRequests:
We see that we can't even tell the difference between an existing and a non-existing webpage when the SOP does not allow us to read their contents!
Beware: *Writing* across origins is still possible by sending GET/POST requests! This opens up the possibility for CSRF attacks!
Possible fixes for CSRF are:
Use CSRF tokens/nonces that are randomly generated by the server for each user and placed in all forms
Double Submit Cookie: require value in posted content to match value of certain cookie (advantage: no server-side state required, just compare submitted form value against cookie; disadvantage: insecure when an attacker can control a sub-domain!)
Attach a custom headers (like 'X-CSRF-Free') to your XMLHttpRequests. This works because cross-domain requests with custom headers require pre-flight CORS requests!
Same-Site Cookies: Strict: never send cookies with cross-origin request (if facebook.com set that, every user following a link there would not be logged in!) Lax Mode (default in Chrome since 2021): cookies only send along with safe requests (GET, HEAD, OPTIONS, TRACE) => protects against POST-based CSRF, not against GET-based though
Examples: Set-Cookie: key=value; SameSite=Strict Set-Cookie: key=value; SameSite=Lax Set-Cookie: key=value; SameSite=None
SOP sort of partly applies to images, we can find out whether an image exists and its size but no more:
Deliberately bypassing the Same Origin Policy:
Sometimes, one wants to deliberately bypass the SOP and communicate cross-origin in mutual consent; there are multiple ways of achieving that (Wikipedia does a good job at listing and summarizing them):
window.name (an old-fashioned ugly work-around, see by clicking the button above)
Domain Relaxation by setting document.domain to the same value:
If two windows (or frames) contain scripts that set domain to the same value, the same-origin policy is relaxed for these two windows, and each window can interact with the other. For example, cooperating scripts in documents loaded from orders.example.com and catalog.example.com might set their document.domain properties to “example.com”, thereby making the documents appear to have the same origin and enabling each document to read properties of the other. Setting this property implicitly sets the port to null, which most browsers will interpret differently from port 80 or even an unspecified port. To assure that access will be allowed by the browser, set the document.domain property of both pages.
Cross-Origin Resource Sharing (CORS):
This standard extends HTTP with a new Origin request header and a new Access-Control-Allow-Origin response header. It allows servers to use a header to explicitly list origins that may request a file or to use a wildcard and allow a file to be requested by any site.
Cross-document messaging using the postMessage() method:
[C]ross-document messaging allows a script from one page to pass textual messages to a script on another page regardless of the script origins. Calling the postMessage() method on a Window object asynchronously fires an "onmessage" event in that window, triggering any user-defined event handlers. A script in one page still cannot directly access methods or variables in the other page, but they can communicate safely through this message-passing technique.
JSONP (JSON with Padding):
This "historical" technique is essentially "XSSI on purpose":
Since HTML <script> elements are allowed to retrieve and execute content from other domains, a page can bypass the same-origin policy and receive JSON data from a different domain by loading a resource that returns a JSONP payload. JSONP payloads consist of an internal JSON payload wrapped by a pre-defined function call [the padding "P" in "JSONP"]. When the script resource is loaded by the browser, the designated callback function will be invoked to process the wrapped JSON payload.
DANGER: JSONP fetches and executes arbitrary JavaScript code. This opens up an XSS vector when an attacker takes control over the site that provides the JSONP API!
Try out Domain Relaxation:
Try out Cross-document messaging using postMessage():
Send message:
Received messages:
See JSONP in action with the MediaWiki/Wikipedia API:
On this site: <script src="https://en.wikipedia.org/w/api.php?action=query&list=random&rnlimit=3&format=json&callback=jsonp_callback"></script>
The script included looks somewhat like this: /**/jsonp_callback({ /* ... */ })
Also on this site: <script>var jsonp_callback = function (response) { /* ... */ }</script>
Result (populated by the jsonp_callback function defined on this site):
http://same-origin-policy.com/cors_example.php is a useful site that responds with the Access-Control-Allow-Origin: * HTTP response header.
It can also respond with another value for the Access-Control-Allow-Origin header when setting a specific GET parameter, see below.
We can read the result of sending an XMLHttpRequest GET request to http://same-origin-policy.com/cors_example.php because the Access-Control-Allow-Origin: * header is being received:
And these are all the response headers received (xmlHttp.getAllResponseHeaders()); we can read those because Access-Control-Expose-Headers: * is also set:
Receiving Access-Control-Allow-Origin: http://same-origin-policy.info instead of Access-Control-Allow-Origin: * should also work:
However, when receiving Access-Control-Allow-Origin: http://some-other-site.com in the response, the browser blocks access for us: