skip to content
usubeni fantasy logo Usubeni Fantasy

Explain the Same Origin Policy thoroughly

/ 9 min read

This Post is Available In: CN EN ES JA

The Pain of Front-End Newcomers

I would like to refer to the Same-origin policy (SOP, which may be abbreviated as SOP below) as the pain of front-end newcomers.

First, let’s briefly discuss what same-origin is: the same protocol, the same host, and the same port are considered the same origin. Taking as an example, the protocol is http, the host is, and the port is 80.

There are some strange restrictions when accessing resources from different origins, so let’s list these situations below together.


Canvas Pollution

Tainted (write-only) canvas, you cannot retrieve images from the canvas, similar situations also occur when loading webGL resources:

Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.

Accessing Information Inside Iframes

Accessing most of the information inside an iframe will be rejected:

Uncaught DOMException: Blocked a frame with origin "http://localhost:5000" from accessing a cross-origin frame.

Ajax Requests Fail

Finally, the most familiar to everyone, Ajax requests fail:

Access to fetch at '' from origin 'http://localhost:5000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

No Restrictions

Nevertheless, despite the many restrictions brought by the same-origin policy, there are still some ways to allow us to use resources from different origins:

  • Links, redirects, and form submissions
  • <script src="…"></script>
  • <link rel="stylesheet" href="…">
  • <img>, <video>, <audio>, <object>, <embed> tags
  • Fonts applied with @font-face
  • Loading web pages in iframes (can be prevented through X-Frame-Options)

But why is that? Why can we access data using the above methods despite the strict same-origin policy? In fact, the essence of it all is—


In fact, all the unrestricted scenarios can only access data but not modify it. As the saying goes, you can look but you can’t touch.

All the methods mentioned above are only for read-only access. Anything you obtain cannot be modified. Opening a page in an iframe allows you to see the entire page, but you cannot interfere with the operation of the page’s program.

Among them, the form’s action is a historical legacy issue. It seems that using post to submit data is a modification operation, but in reality, it cannot access the returned data. Furthermore, after submission, it will directly redirect to the target URL, similar to links and redirects. Back in the day, this was heavily used during the PHP era.

So, for Ajax requests, the essence is also that non-same-origin resources cannot be written to (opaque response). Your request is successful, but the browser refuses to let you manipulate the retrieved data. In other words, intercepting cross-origin request results is a browser behavior.

The Purpose of Its Existence

Knowing the essence makes it easy to understand why SOP exists:

Translate into English:

  • Network security issues in mainly prevent Cross-Site Request Forgery (CSRF)
  • Prevent the host from arbitrarily operating iframes
  • Prohibit clients from arbitrarily editing non-origin images, audio, and video files

Response Solutions

SOP brings secure user experience, but the development experience is not very good, and developers need to make extra efforts to deal with SOP.

CORS Protocol

To allow sharing responses cross-origin and allow for more versatile fetches than possible with HTML’s form element, the CORS protocol exists. It is layered on top of HTTP and allows responses to declare they can be shared with other origins.

The CORS protocol is used to negotiate whether server resources can be accessed by different origins.

That’s right, CORS is a protocol, a protocol based on the HTTP protocol, implemented through HTTP request and response headers. The specific process is as follows:

  1. Encountering a cross-origin request, and it is not a simple request (otherwise, the actual request is sent directly)
  2. Sending a preflight request
  3. The server returns CORS configuration information
  4. The browser determines whether to allow the actual request to be sent
  5. Sending the actual request, and the response headers of the actual request will also contain CORS configuration information, so the browser will allow developers to use the response data (if it is a simple request, it will be sent directly, and if the configuration information check fails at this time, a cross-origin error will be reported)

A preflight request (often translated as a preflight request) is an OPTIONS HTTP request, with the key request headers being:

  • Access-Control-Request-Method: Method of the actual request
  • Access-Control-Request-Headers: Headers included in the actual request

The CORS protocol will return the conditions for allowing cross-origin requests through HTTP response headers. From the naming, we can basically understand the functions of these response headers in the CORS protocol:

  • Access-Control-Allow-Methods: Allowed methods
  • Access-Control-Allow-Headers: Allowed headers
  • Access-Control-Allow-Origin: Allowed source
  • Access-Control-Allow-Credentials: Whether to allow credentials to be carried when accessing
  • Access-Control-Max-Age: Cache time for the above two pieces of information
  • Access-Control-Expose-Headers: Response headers that JavaScript can read

So why do we need preflight requests?

In my understanding, as mentioned earlier, the behavior of the browser is to intercept the result, and the request will still be successfully sent to the server and execute the logic normally, which is too dangerous. However, with preflight requests, the actual request is intercepted before it is successful in reaching the server. On the other hand, the CORS protocol does not block simple requests from reaching the server, probably because the GET method does not bring about data modification, making it difficult to cause serious consequences, combined with possible historical reasons, and hence is allowed.

Below is a simple implementation of a server-side CORS protocol:

fastify.addHook("preHandler", (req, res, done) => {
const allowedPaths = ["/cors-simple", "/cors"];
console.log(`\n${req.method}: ${req.url}\n`);
if (allowedPaths.includes(req.url)) {
res.header("Access-Control-Allow-Origin", "");
res.header("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE");
res.header("Access-Control-Allow-Headers", "content-type,custom-header");
res.header("Access-Control-Allow-credentials", "true");
const isPreflight = /options/i.test(req.method);
if (isPreflight) {
return res.send();

A more mature solution can be found in fastify-cors.

Once the CORS protocol is negotiated, the browser will allow JavaScript to access cross-origin data. But the problem is not so easily solved (that’s why I said the development experience is not so good), even though cross-origin data can be read, there are still issues with credentials such as cookies:

The fetch method itself does not, by default, carry cross-origin cookies, nor does it set the set-cookie in the response. It must have credentials: "include" set, but we need to do more than that, we still need to:

  • After adding Access-Control-Allow-Credentials, allow requests sent to cross-origin to carry credentials
  • But after configuring Allow-Credentials, Access-Control-Allow-Origin cannot be *, it must be a single Origin
  • Only SameSite=None cookies can be sent to the server
  • When SameSite=None, Secure is also necessary
  • Must use the https protocol, otherwise Secure cookies cannot be written

P.S. There are subtle differences between same site and same origin, but in most cases they are not needed, for more details please refer to: The great SameSite confusion

Starting from chorme80 (2020.02), headers HTTP header: Set-Cookie: SameSite: Defaults to Lax is introduced. At this point, users will feel that after the upgrade they can’t log in, it’s because cross-origin cookies are not sent by default.

By the way, there are also CORP request headers similar to CORS, this request header is used to forbid <script>, <img> and other resource references, with the default value being cross-origin.

Today, browsers act as though Cross-Origin-Resource-Policy: cross-origin is set on every response that lacks an explicit CORP header.

Clean Canvas

The CORS protocol also applies to resolving the situation where images are read by the canvas. By default, the image request method is not CORS, and you need to add img.crossOrigin = 'anonymous' (note that when added using JavaScript, camel case is required; all lowercase does not work, but when added to an HTML tag, it should be all lowercase) to change the request method.

The difference now is that although the canvas could originally display cross-origin images directly, after adding crossOrigin, only resources with added CORS request headers can be successfully requested. Otherwise, a direct request will result in an error, and the image will not be displayed:

Access to image at '' from origin 'http://localhost:5000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Successfully requested cross-origin images can be added to the canvas without polluting it.

Reverse Proxy

Apart from adding CORS, you can also use a reverse proxy to integrate originally cross-origin API services and network application files into the same domain.

In the development environment, you can use familiar tools such as Webpack devServer.proxy and Vite’s Server Options.

In production environments, nginx is a commonly used reverse proxy.


postMessage mainly addresses data exchange between different windows (e.g., different iframes):


targetWindow.postMessage(message, targetOrigin, transfer);

To send information to another window using postMessage, you first need to obtain the window variable of the target window. For example: for an iframe, you can access the contentWindow property after using querySelector; the method will directly return the window object of the target window, and so on.

The first parameter is the data being sent, and it will be deeply cloned. Additionally, you can control the second parameter targetOrigin to ensure that the origin of the target window is the value you specify.

In practical use, it might look like this:

iframe.contentWindow.postMessage({ jsondata: {}, 1: "hello" }, "http://localhost:3000");


window.addEventListener("message", (event) => {
if (event.origin === "") {

This approach works because it requires both parties to add encoding. Therefore, we can ensure that the communicating parties are trustworthy through some agreements. The simplest method is to use event.origin to determine if the information source is from a trusted source, otherwise, it could be susceptible to attacks.


Websocket can bypass the same-origin policy, but it puts a heavy load on the server. So, it is unlikely that anyone would use websocket to replace HTTP interfaces just to bypass the same-origin policy.


## Summary
- The same protocol, host, and port are considered the same origin
- The essence of the Same-Origin Policy (SOP) is to ensure that information from different origins can only be read
- SOP provides users with a more secure network environment
- Currently, the primary way for developers to deal with cross-origin restrictions is the CORS protocol
- Front-end developers still need to handle cross-origin cookie issues based on CORS
- `postMessage` can be used for cross-origin communication with iframes
## References
- [Same-origin policy](
- [Allowing cross-origin use of images and canvas](
- [Using textures in WebGL](
- [The great SameSite confusion](
- [Loading web pages](
- [Special reference materials for experts](
P.S. Due to the rapid updates in browsers, the same-origin policy mentioned in the text may change, so please be careful to discern.