Cross Origin Resource Sharing (CORS) policy is introduced in HTML5 specification. It allows control over cross domain calls and the application can control over the resource or content. CORS specifications are part of W3C specifications and posted over here - http://www.w3.org/TR/cors/. In the specification extra HTTP headers are added to control the content delivery and loading mechanism. Hence, both HTTP request and response would have few extra headers and they are validated before the actual usage.
The core idea of this specification is to have a key decision based on “origin”. Every request will have their own origin based on their current domain. For example, a browser is at the URL say http://www.store.com/ then their origin at this point is www.store.com. If the loaded page would like to use the API to make a call to say http://google.com then HTTP request will send “origin” information to the server and now it’s up to the server to treat this call or not. Having this mechanism in place empowers the target domain to decide whether or not to share resources over the Internet. This gives a close control over resource and content sharing for every domain and application. XHR level 1 APIs were not allowed to make a cross domain call but with XHR level 2 specifications one can make cross domain calls provided they abide by CORS. This is a major shift in Internet paradigm and applications now get seamless control over various domains and their content. It adds a security risk as an obvious outcome.
The following are extra headers that are added for HTTP requests. Hence, when a browser transmits HTTP requests that originate from APIs like XHR, it automatically adds some of these headers.
Origin
Access-Control-Request-Method
Access-Control-Request-Headers
Similarly, when applications send the response back over HTTP, they will also add a range of headers. The browser can take decisions based on these headers and accordingly load content in browser’s DOM (or not load at all). This gives close control and provides a feedback loop. The following are HTTP headers in the HTTP response.
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Expose-Headers
Access-Control-Allow-Max-Age
Access-Control-Allow-Allow-Methods
Access-Control-Allow-Allow-Headers
This is a much better way of controlling the content over the old method like JSONP. In the past we could make cross domain calls and involve JSON with Padding (JOSNP). For this we make a call to a cross domain using the SCRIPT tag and pass on the function name as a parameter. The cross domain application can thendecide whether to serve or not, and if it decides to serve, then it will send JSON content padded with the same function name. This will automatically get executed in the browser.
For example
The browser will have a function say setProfile like below.
The browser will have a function say setProfile like below.
Function setProfile(data){
// do some stuff on JSON
}
Now, when an application wants to make a cross domain call to say http://example.com/ then it will send a request as below.
http://example.com/JSONfeed?name=shah&callback=setProfile
If http://example.com/ would like to serve this call then it will create a JSON for profile where name is shah as mentioned in the URL and wrap with function setProfile like below.
setProfile(JSON-data)
setProfile(JSON-data)
Now, the browser will pass on the JSON-data to the function setProfile and execution will begin from that point onwards. This sounds very bad and not so graceful way to bypass the same origin policy. To avoid this type of methodology, CORS has been implemented in the browser.
Inner working of the CORS
CORS is implemented at the browser end and if developers want to share resources with a cross domain then they can add appropriate code and support at the server end. Hence, the focus of the CORS is primarily at the browser end.
Let’s take two hypothetical domains – redacme.com and blueacme.com. We load a page from redacme.com and it has the following function.
Let’s take two hypothetical domains – redacme.com and blueacme.com. We load a page from redacme.com and it has the following function.
function getMe()
{
var http;
http = new XMLHttpRequest();
http.open("GET", "http://blueacme.com/", true);
http.onreadystatechange = function()
{
if (http.readyState == 4) {
var response = http.responseText;
alert(response);
document.getElementById('result').innerHTML = response;
}
}
http.send();
}
This getMe() function is simple XHR call. It is making a cross domain GET call to blueacme.com and try to load the response into specific locations within the DOM defined by “div” tag. When this function being called we get the following request on the wire (Chrome being used)
GET / HTTP/1.1
Host: blueacme.com
Proxy-Connection: keep-alive
Origin: http://redacme.com
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.75 Safari/537.1
Accept: */*
Referer: http://redacme.com/cors.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
As you can see there is one extra HTTP header being added to this particular request and it is as shown below.
Origin: http://redacme.com
This defines the origin from which the call is initiated. The HTTP request is being made irrespective of the policy which is defined on the server.
The server will send following response headers.
HTTP/1.1 200 OK
Date: Sun, 12 Aug 2012 08:01:57 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 13539
As no CORS specific headers were sent, this application does not allow the pages to be consumed by the browser. Hence, the page will not get loaded in the browser.
The browser will throw the following error on the console.
XMLHttpRequest cannot load http://blueacme.com/. Origin http://redacme.com is not allowed by Access-Control-Allow-Origin.
This is where CORS kicks in - if “Access-Control-Allow-Origin” is set to “*” or a specific domain then the browser will load the page.
For example, if we were to have the following response then the browser will load the page on redacme.com since it allowed.
Access-Control-Allow-Origin: http://redacme.com
Additionally, the following response header will allow content to be loaded on any domain since the allow access is set to “*”
Access-Control-Allow-Origin: *
Preflight call and negotiation
There are certain limitations at the CORS level. If you want to change certain key parameters like method, content-type etc., then that particular call will not get initiated by the browser but it will make a preflight call to verify the CORS policy set on the server side. Hence, it will send first an additional request in terms of OPTION and then analyze the response. If HTTP response allows that particular method or content-type with respect to the origin then it will make next request.
Consider the following routine.
Consider the following routine.
function getMe()
{
var http;
http = new XMLHttpRequest();
http.open("POST", "http://blueacme.com/", true);
http.onreadystatechange = function()
{
if (http.readyState == 4) {
var response = http.responseText;
alert(response);
document.getElementById('result').innerHTML = response;
}
}
http.send("test");
}
We are making POST request and sending some data onto the server. This will actually fire the following HTTP request on the wire to blueacme.com
OPTIONS / HTTP/1.1
Host: blueacme.com
Proxy-Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://redacme.com
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.77 Safari/537.1
Access-Control-Request-Headers: origin, content-type
Accept: */*
Referer: http://redacme.com/cors.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
You can see some additional headers that are added from CORS perspective. They are as follows.
Access-Control-Request-Method: POST
Origin: http://redacme.com
Access-Control-Request-Headers: origin, content-type
Here, the browser asks the application/server whether it supports these extra HTTP headers or not. If the server responds with allow access true for this particular origin then the browser will make the actual call else it will not initiate the HTTP POST request. Preflight call gives some initial protection against attack vectors like CSRF and so on.