Same-origin policy (SOP)

What is the same-origin policy (SOP)?

The same-origin policy (SOP) is a web security mechanism built into web browsers that influences how websites can access one another. Without SOP, a malicious website or web application could access another without restrictions. That would allow attackers to easily steal sensitive information from other websites or even perform actions on other sites without user consent.

SOP does not need to be turned on – it is automatically enabled in every browser that supports it. Developers must be aware of this mechanism when creating web apps that communicate with one another, so they know how to disable it in special circumstances safely.

The same-origin policy is often confused with content security policies. The difference is that content security policies prevent calls to external resources (outbound) while the same-origin policy prevents calls from external resources (inbound). Also, content security policies are not enabled by default and must be defined by developers.

What does SOP protect against?

The SOP mechanism was designed to protect against attacks such as cross-site request forgery (CSRF), which basically attempt to take advantage of vulnerabilities due to differing origins. However, because it’s just a browser security policy and has never been defined as a permanent Internet specification, every browser implements it a bit differently. You should thus be careful with relying on SOP because the user may be running a browser with different SOP rules (for example, Internet Explorer).

Note that SOP is entirely useless as a method of protection against cross-site scripting (XSS) because it would have to limit the loading of scripts from different sites, ultimately hindering web application functionality.

How does the same-origin policy work?

To understand the same-origin policy, we need first to understand the term origin.

In web terms, the origin is a set of common characteristics of a web resource. In most cases, the origin is a combination of three elements: the schema (protocol), the hostname (domain/subdomain), and the port. Therefore, all resources identified by the same schema:hostname:port combination have the same origin. However, if two resources differ in any one of these three elements, modern browsers such as Google Chrome or Mozilla Firefox treat these resources as having a different origin. Only in the case of Microsoft Internet Explorer the port number is not considered a part of the origin. For example:

  • http://www.example.com/page.html and http://www.example.com/subpage/page2.html HTML documents have the same origin: the protocol is HTTP, the domain is www.example.com, and the port is 80.
  • http://www.example.com/page.html and https://www.example.com/page.html have different origins due to differing protocols (HTTP vs HTTPS).
  • http://www.example.com/page.html and http://example.com/page.html have different origins because the subdomain (hostname) is different (www.example.com vs example.com).
  • http://www.example.com/page.html and http://www.example.com/page.html:8080 have different origins due to differing ports (80 vs 8080). However, in Internet Explorer they have the same origin.

When does the browser use SOP?

Origin checks are applied by the browser in every case of potential interaction between elements from different origins. This includes, for example:

  • JavaScript code and the Document Object Model (DOM), like when a page cannot access the content of its iframe unless they are of the same origin.
  • Cookies, such as your session cookie that is used for authentication for a certain site and cannot be sent to a page with a different origin. Note that for cookies, the schema and port are not evaluated, only the domain/subdomain.
  • AJAX calls (XmlHTTPRequest).

However, SOP does not completely eliminate the interaction between different origins. The browser evaluates whether a specific interaction could pose a threat and if not, it is allowed. For example:

  • You can usually write between origins. For example, you can create cross-origin links and submit forms cross-origin.
  • You can usually embed between origins. For example, you can use content from a different origin in an iframe (if X-Frame-Options allows it) or embed an img, a css, or a script element from a different site.
  • However, read access between origins is usually blocked. This often means that you can send a cross-origin request but cannot read the reply.

How to relax SOP restrictions

In some cases, you may want to loosen the tight grip of SOP and allow certain cross-origin interactions, for example, between different domains that both belong to you. In such cases, there are several ways to ensure that SOP does not hinder your web application’s cross-domain interaction capabilities.

Declaring the origin

The simplest way to change the origin of your site is by declaring it using JavaScript:

document.domain = "example.com";

However, this is only possible for sites within the same domain hierarchy – otherwise, any site could pretend to have your origin. For example, you can use this simple method if you have several microsites under different subdomains, such as login.example.com, blog.example.com, etc.

Note that browsers are on their way to deprecating this approach, so this method is not recommended.

Using the postMessage method

If you need to communicate between window objects such as a page and a popup from that page or a page and an iframe embedded on that page, you can use the window.postMessage() method.

For example, you could get a reference to another window using newWindow = window.opener and then dispatch a message event through newWindow.postMessage(). The newWindow uses the event object to access arguments passed with this method.

Cross-origin resource sharing

Cross-origin resource sharing (CORS) is an HTTP mechanism that uses HTTP headers to define origin permissions. Using CORS headers, you can inform the browser that resources from another origin have the right to access resources on your page.

For example, a GET request to a site may be sent with an Origin request header that declares the exact origin (similar to document.domain):

GET / HTTP/1.1
Host: www.example.com
(...)
Origin: http://example2.com

In response, the resource that supports CORS will send an Access-Control-Allow-Origin response header:


HTTP/1.1 200 OK
(...)
Access-Control-Allow-Origin: http://example2.com
(...)

The Access-Control-Allow-Origin header may declare a single origin or a wildcard (*). Of course, using wildcards can be risky, but the option is there for the web application developer.

Preflight requests

The above simple scheme is used for HTTP requests that the web browser considers safe. For more risky requests, the web browser first makes sure that cross-origin communication is allowed using a special preflight request. Preflight is required in the following cases:

  • If there is a custom HTTP header present in the request (any other header except Accept, Accept-Language, Content-Language, Content-Type*, Range).
  • If the method of the request is not GET, HEAD, or POST.
  • If the request is a POST request, but the Content-Type is not text/plain, multipart/form-data, or application/x-www-form-urlencoded.
  • If the XMLHttpRequestUpload object has at least one event listener registered on it.
  • If you use a ReadableStream object in the request.

The preflight request is an OPTIONS request with CORS headers:

OPTIONS / HTTP/1.1
Host: www.example.com
(...)
Origin: http://example2.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-CUSTOM, Content-Type

In response, the server informs the browser what methods are allowed, whether it accepts the headers, and for how long the preflight request is valid:

HTTP/1.1 204 No Content
(...)
Access-Control-Allow-Origin: http://example2.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-CUSTOM, Content-Type
Access-Control-Max-Age: 86400

After the preflight is complete, you can send regular requests with CORS headers.

Using WebSockets

If your script attempts to connect to a WebSocket, browsers will allow all such communications without checking the same-origin policy. However, the browser adds an Origin header to the request, specifying the origin of the script from which the connection is coming.

In such a case, it is not the browser but the developer that is expected to ensure safety. If you use WebSockets in this way, you should include functionality on the WebSocket server to compare data in the Origin header with a whitelist of origins that are safe to reply to or use other ways to confirm that a connection can be trusted.

Using JSONP (not recommended)

Before cross-origin resource sharing was introduced in 2009, web pages could use JSONP (JSON with padding) to bypass the same-origin policy. This was done by using <script> tags to retrieve and execute JSON content from other origins. However, when using JSONP, an attacker may be able to replace the original function with a malicious one. Therefore, currently, the use of JSONP is not recommended.

Related whitepapers


Written by: Tomasz Andrzej Nidecki, reviewed by: Aleksei Tiurin