Why doesn't a simple HTTP request to display a remote web page violate the same-origin policy?

And how can a web server distinguish between requests coming from a script and coming from a user?

It doesn't. The same origin policy is enforced by the browser, not the server.

The purpose of the Same Origin Policy (SOP) isn't to protect the server itself. Instead it's to protect confidential information which the server wishes to share with the user, but not to share with other parties. This information may be protected by checking the user's cookie, authentication header, or IP address when they send a request, but those checks can be bypassed by an attacker getting the legitimate user to open the attacker's website with a script to request the information.

This is when the SOP provides protection. The request may still be sent, but the browser can refuse to allow the script to see the information in the response.

If there is a need to protect the server against potentially harmful requests that it could be tricked into carrying out based on its trust in the user, the SOP is not enough. At that point the server needs other techniques to defend against CSRF.


If one enters a URL in the browser one starts with a new empty origin, i.e. no domain and port belong to the origin initially. Everything can be put into a window/tab with an empty origin and once it is put there the origin changes depending on where the data came from.

If one instead calls a HTTP request from inside a loaded web page, one starts with a non-empty origin. In this case the same origin policy comes into action and restricts what can be done from inside this non-empty origin.

Note that if one has already a loaded page in the browser and now replaces the URL in the URL bar, the same origin policy does not apply since this new URL is not called from inside the window/tab but from outside. Thus it will again start with an empty origin.


The simple answer to your question is that "requests to display a web page" are what set the origin, so obviously they cannot violate same-origin policy. Things that happen within a page (such as JS execution and notably XHR/Fetch) are subject to various restrictions due to same-origin policy, but top-level navigation is always allowed*.

* Iframes in general, and sandboxed ones in particular, get a bit weird here. An iframe is part of the parent page's origin, but its content is part of the origin of the iframe's src (which could be totally different!). Cross-orign parent/iframe relationships have severely limited interaction, similar to any two cross-origin pages, with the notable exception that by default either can navigate the other (that is, pages can set the src of an iframe they contain, and iframes can set the location of their parent (although the iframe cannot set the location to a javascript: or data: URI, as that would be injecting content into the parent's origin). It is possible to sandbox iframes such that they cannot perform parent navigation... or indeed such that they can't execute JS at all!