The same-origin policy
For security reasons, the browser’s same-origin policy restricts a document or script from one origin (scheme(protocol), host(domain), and port of a URL) to interact with a resource from another origin. For example, a page (say A) embeds an iframe
(say B) which is from another site (cross-origin resource embedding is typically allowed), a script in A can not read content from B.
Origin of a URL
Some special URLs:
about:blank
(like the URL of a blank window opened byWindow.open('')
) orjavascript:
Scripts in such pages inherit the origin of the document containing that URL, since these types of URLs do not contain information about an origin server.
data:
A data URL starts with thedata:
.MDN: Data URLs: Data URLs are treated as unique opaque origins by modern browsers, rather than inheriting the origin of the settings object responsible for the navigation.
file:///
A file origin uses a URL starts withfile:///
schema. Modern browsers usually treat file origins as opaque origins.Some browsers don’t treat files from the same folder as the same origin, but some browsers do.
See more at Same-origin policy – Web security | MDN.
What is allowed and what is disallowed
For networking APIs, the same-origin policy distinguishes between sending and receiving information. Broadly, one origin is permitted to send information to another origin, but one origin is not permitted to receive information from another origin.
Interactions between two different origins are typically placed into three categories:
- Cross-origin writing, typically allowed. Examples are links, redirects, and form submissions.
Form submission from other websites can be dangerous, like CSRF(Cross-Site Request Forgery) attacks. If you have a server, you should protect against CSRF attacks (see more in the next section).
-
Cross-origin embedding, typically allowed.
Examples:
- “
-
“ (requires a correct
Content-Type
header). -
<img src="...">
-
<video>
and<audio>
-
<object>
and “ -
@font-face
depends the browser. -
“
-
Cross-origin reading, typically disallowed. But read access is often leaked by embedding. Like in below example, your script is able to access the size of an embedded image from another origin. However, reading data of the image is prohibited by the same-origin policy.
<img id="icon" src="otherdomain.com/icon.png"> // Access the size of an embeded image from another origin. const img = document.getElementById('icon'); console.log('image size:', img.width, img.height); // OK // Output: image size: 64 64 // Draw the image on canvas. const context = document.getElementById('canvas').getContext('2d'); context.drawImage(img, 0, 0); // OK // Read data of the image. Failed (Chrome): // Uncaught DOMException: Failed to execute 'getImageData' on // 'CanvasRenderingContext2D': The canvas has been tainted by // cross-origin data. const imgData = context.getImageData(0, 0, 32, 32); context.putImageData(imgData, 64, 64);
How to block those allowed cross-origin access
Under the same-origin policy, some allowed cross-origin access is also dangerous, like “clickjacking” and CSRF attacks. After all, the same-origin policy should not block all, like cross-site hyperlinks. You need to do extra effort to protect your site.
Some methods to prevent cross-origin access and some attacks:
- Use
X-Frame-Options
response header to prevent embedding your siteA site can config its web server to send
X-Frame-Options
header to indicate whether or not a browser should be allowed to render a page in a](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame), [
, “ or<object>
. It can be used to ensure that its content is not embedded into other sites, and hence avoiding “clickjacking” attacks.A “clickjacking” attack is that a bad site embeds an
iframe
and adds a fake button to trick you. One trick is putting a transparent button leading to a fake site on top of theiframe
, the other is putting something to cover theiframe
and adds a button on it. In the second case, the underneathiframe
or its button is what the attacker wants you to click on. -
Put a token in the request and check it in the server to prevent CSRF attacks
According to CSRF, Cross-Site Request Forgery (CSRF) is an attack that tricks an end user to submit a malicious request on a site or a web application in which they’re currently authenticated.
For example, you have signed into a mail site and clicks a malicious link in an email from an attacker. By clicking the link you probably makes a request something like changing password to the mail server. For most sites, browser requests automatically include your credentials since you are authenticated and the sever would think it is a legitimate request from you.
In an CSRF attack, its target is to trick the victim to make a state-changing request of the attacker’s desire. A request to retrieve data will not help the attacker because the attacker does not receive the response.
Note an CSRF attack can also be done by a form submission. A forged form is hosted in an attacker’s website. When the victim clicks a link it starts to loading the attacker’s page and the form can be automatically submitted using JavaScript which is trigged by the
load
event.
Cross-Origin Resource Sharing (CORS)
As said above, reading a resource from another origin is typically disallowed by the same-origin policy. But there are such legal needs, like you want to read public data from a different origin. CORS provides support for this safely.
CORS is a mechanism that lets a server to use a HTTP header to specify any other origins except its own from which a browser should permit loading resources.
When you make a request for a resource to another origin, whether it is permitted by the browser depends on the response returned by the resource-providing server.
- If the response has a header
Access-Control-Allow-Origin
with a value which tells the browser to allow the origin to access that resource, then the request is permitted. -
Otherwise, the request is blocked by the browser.
An example that demonstrates how CORS works
In below example, we make two request to a server separately, one is when the server does not enables CORS, the other is when the server enables CORS.
The example can be run on your own computer locally.
test-cors.html
makes requests.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<h1 id="fetch-result"></h1>
<script>
const fetchResult = document.getElementById('fetch-result');
fetch('http://localhost:8080/')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not OK');
}
return response.text();
})
.then(text => {
fetchResult.innerText = text;
})
.catch(error => {
fetchResult.innerText = error;
});
</script>
</body>
</html>
Create a web server with Nodejs. The server just respond with a string for all requests.
server.js
const http = require('http');
const server = http.createServer(function (req, res) {
res.setHeader('Content-Type', 'text/html');
res.end('Hello');
});
server.listen(8080);
Run the server in a shell:
$ node server.js
Open the test-cors.html
in an browser, you will see below error in the broswer’s console. The request made by fetch()
is blocked by the browser for the response has no CORS header.
Access to fetch at 'http://localhost:8080/' from origin 'null' 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.
Now we make the server allow CROS by adding a header with 'Access-Control-Allow-Origin: *
(*
means the resource can be accessed by any origin) header in the response:
const http = require('http');
const server = http.createServer(function (req, res) {
res.setHeader('Access-Control-Allow-Origin', '*'); // Enable CORS.
res.setHeader('Content-Type', 'text/html');
res.end('Hello');
});
server.listen(8080);
Press ctrl+c
to stop the web server and restart it with node server.js
.
Refresh test-cors.html
and you will see the request is successful with a respond Hello
.
Preflight request before an complex cross-origin request
In the previous example, the request just get simple resource and the browser does not make a preflight request to the server before the actual one.
For other requests like a request with a Content-Type
header value other than application/x-www-form-urlencoded
, multipart/form-data
, or text/plain
, the CORS specification mandates the browser first send a preflight request to check whether the server will approve the actual request. And in this communication the server can inform the browser what information (such as Cookies and HTTP Authentication ) is needed in the latter actual request.
See more about for which requests that the browser send preflight requests at MDN: CORS- preflighted requests.