Working With and Around the Same-Origin Policy
As a JavaScript developer, you likely use Ajax extensively to exchange data with a server or update a web page without refreshing. Although sending an Ajax request to your server is a pretty straight forward request, exchanging data with a server on another domain is a different story altogether. Let us try it out!
Let us run the following from http://www.mysite.com
(or 127.0.0.1
/ localhost
) on Chrome 32.
request = new XMLHttpRequest;
request.open('GET', 'http://myothersite.com/', true);
request.send();
You will receive an error. XMLHttpRequest
cannot load http://myothersite.com/
. No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin http://www.mysite.com
is therefore not allowed access.
Why does this happen? Didn’t we do everything right?
Same-origin Policy
The same-origin policy permits scripts running in a browser to only make requests to pages on the same domain. This means that requests must have the same URI scheme, hostname, and port number. This post on the Mozilla Developer Network clearly defines the definition of an origin and when requests result in failure. If you send a request from http://www.mysite.com/
, the following types of requests result in failure.
https://www.mysite.com/
– Different protocol (or URI scheme).http://www.mysite.com:8080/myUrl
– Different port (since HTTP requests run on port80
by default).http://www.myothersite.com/
– Different domain.http://mysite.com/
– Treated as a different domain as it requires the exact match (Notice there is nowww.
).
Changing Origin
Occasionally, the same origin policy may block requests between subdomains on the same domain. The easiest way to solve this problem is to set document.domain
from within JavaScript. For example:
document.domain = 'mysite.com';
Note that the port number is stored separately. Making one domain interact with another on a different port (which is the case with chat applications), would require something different. Even setting document.domain = document.domain
, which overwrites the port number to null
will not help in getting this done.
Using a Web Proxy
Although specifying document.domain
helps you contact subdomains of your own website, what would you do if you needed to access data from a different domain altogether? An interesting, yet easy to understand, approach is to use a web proxy on your own server.
Instead of sending a request directly from your domain (http://www.mysite.com/
) to a new domain (http://www.myothersite.com/
), you instead send a request to your own server (http://www.mysite.com/connect/
), which in turns sends a request to the new domain (http://www.myothersite.com/
). To the browser, it appears you are exchanging the data with your own server. In reality, in the background, you have accessed data on a new domain from your server. A flowchart to explain the process is shown below.
Source: Yahoo Developers
Using JSONP
Another way of implementing cross browser requests is by using JSONP, or “JSON with padding.” JSONP takes advantage of the fact that <script>
tags are not subject to the same-origin policy. For example, you can include a library like jQuery on your page even if it is hosted on Google’s CDN.
JSONP requests are made by dynamically requesting a <script>
tag. The interesting part is that the response is JSON wrapped in a function call. When making the request, you specify the function name as a callback function. When the server responds, the callback function (which must exist on your page) is executed with the data returned from the server.
For example, a typical JSON response might look like this:
{
"id": "123",
"name": "Captain Jack Sparrow"
}
The same response can be emulated in a script tag with the callback function myFunction
as shown below.
<script src="http://www.myothersite.com/get_data?callback=myFunction"></script>
The browser downloads the data from the specified URL normally and evalutates it as JavaScript. The response might look like this:
myFunction({"id": "123", "name": "Captain Jack Sparrow"});
myFunction
would then be invoked, allowing your page to process the JSON returned from myothersite.com
.
Security Concerns – Cross Site Request Forgery
Since the <script>
tag doesn’t respect the same-origin policy, it is possible for a malicious website to get sensitive data using the same URL. Using the example above, a malicious page could download the same JSON data and perform some unkind act with it. This is known as a Cross Site Request Forgery (CSRF) attack. Some countermeasures to prevent CSRF attacks include using tokens or cookies for validation, and limiting the lifetime of such tokens.
Cross Domain Resource Sharing
Although JSONP can be used to accomplish most tasks with relative ease, there are several shortcomings. You can only send HTTP GET requests using JSONP. This rules out any possibility of doing CRUD operations cleanly using JSONP. Although this security concern is eliminated with the proxy method, there is another interesting method that will help us interact with RESTful APIs without jumping through hoops.
Cross Domain Resource Sharing, or CORS, works by modifying HTTP headers in your requests to access resources on a different domain. In IE8+, simple CORS requests using the XDomainRequest
(instead of the XMLHttpRequest
) are permitted. A simple example is shown below.
request = new XDomainRequest();
request.open(method, url);
request.onload = function() {
callback(req.responseText);
};
request.send(data);
We have been talking about “simple” requests. Not-so-simple requests refer to preflighted requests, which first send an HTTP request to the other domain to determine whether it is safe to perform that action. A detailed example of a preflighted request and the information exchange between the server and client is explained in this MDN post on CORS.
Conclusion
Although CORS looks like the future of front end programming, you should still use it with care because there is no support for very old browsers (IE7 and earlier). Support for CORS is a minor concern, but you should definitely go ahead and give it a try! For further reading, I suggest you go through MDN’s detailed post on CORS.