When building complex client-side applications, at some point it usually becomes necessary to make Ajax requests to domains other than the one from which your page originated. This is especially true if you are part of a large enterprise with distributed sub-domained resources. Up until recently, this had not been possible due to browser-enforced, same-origin security policies for JavaScript. Over the years, various techniques have been employed to work around this security restriction, such as server-side proxies, JSONP, and iframe proxies using post message.
With the emergence of the Cross Origin Resource Sharing (CORS) specification, now a candidate for W3C Recommendation, web application developers have a browser-supported mechanism to make XmlHttpRequests to another domain in a secure manner.
As of this writing, we can finally say that CORS is supported by all major browsers. It initially appeared in Firefox 3.5, Safari 4, and Chrome 3. Internet Explorer 10 now has native support.
What is a Cross-Origin Request?
If the script on your page is running from domain mydomain.com and would like to request a resource via an XmlHttpRequest or XDomainRequst from domain otherdomain.com, this is a cross-origin request. Historically, for security reasons these types of requests have been prohibited by browsers.
How Does it Work?
The CORS mechanism works by adding HTTP headers to cross-domain HTTP requests and responses. These headers indicate the origin of the request and the server must indicate via headers in the response whether it will serve resources to this origin. This exchange of headers is what makes CORS a secure mechanism. The server must support CORS and indicate that the domain of the client making the request is permitted to do so. The beauty of this mechanism is that it is automatically handled by the browser and web application developers do not need to concern themselves with its details.
Simple Requests
For simple cross-site requests (i.e., GETs and POSTs that don’t set custom headers and the request body is plain text or form data), the browser simply includes additional Origin and Referrer headers indicating the requesting domain. For example to retrieve the resource called some-resource at otherdomain.com using the jQuery Ajax API, a developer would simply write [using CoffeeScript for code examples]:
$.get( url: 'http://otherdomain.com/some-resource' ).done successFn
and the browser will issue a request with the following headers:
GET http://otherdomain.com/some-resource/ HTTP/1.1 Referer: http://mydomain.com/myapp/ Origin: http://mydomain.com
Note that the browser will only include the Origin header when the request is cross-origin. A CORS-enabled server receiving this request will include these headers in its response:
Access-Control-Allow-Origin: http://mydomain.com Content-Type: application/json
When the browser sees that the Access-Control-Allow-Origin value matches the domain of the page, it will permit the response to be processed. A server can set a value of “*” in this header to indicate that it is a public resource that allows any origin.
Preflighted Requests
The CORS specification requires browsers to preflight requests that:
- Use request methods other than GET, POST, or HEAD
- Have custom headers
- Have request bodies with Content-Type other than text/plain, application/x-www-form-urlencoded, or multipart/form-data
A preflighted request utilizes the OPTIONS request method to verify that the server is CORS-enabled and supports the type of request the client would like to send. For example, to update the resource called some-resource at otherdomain.com and also set a customer header called X-Foo, a developer would write:
$.ajax( url: 'http://otherdomain.com/some-resource' type: 'PUT' headers: 'X-Foo': 'bar' data: someResource ).done successFn
The browser will first issue a request with the following headers:
OPTIONS http://otherdomain.com/some-resource/ HTTP/1.1 Origin: http://mydomain.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Foo
The CORS-enabled server will respond with response headers indicating that PUT is an allowable request method, X-Foo is an allowable request header, and the results of this preflight request can be cached for 3600 seconds.
HTTP/1.1 200 OK Access-Control-Allow-Origin: http://mydomain.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: X-Foo Access-Control-Max-Age: 3600
Then the browser will send the actual PUT request.
PUT http://otherdomain.com/some-resource/ HTTP/1.1 Content-Type: application/xml; charset=UTF-8 X-Foo: bar
The preflight mechanism ensures among other things that servers that are not CORS-enabled will not process a request that might modify server resources as a side effect prior to the browser disallowing the response because it lacks the proper Access-Control-Allow-Origin header.
Credentialed Requests
A browser will not send Cookies or HTTP Auth information in a cross-domain XmlHttpRequst. The client application must indicate that they should be sent by setting the withCredentials property of the XmlHttpRequest or XDomainRequest. By default, this value is false and not set. From the simple request example above, if we wanted to include any cookies that the user may have acquired for domain otherdomain.com along with the request to retrieve the resource called some-resource at otherdomain.com the client would setup its Ajax request as follows:
$.get( url: 'http://otherdomain.com/some-resource' xhrFields: 'withCredentials': true ).done successFn
Note that you must use jQuery 1.5.1+ for this to work as prior versions of jQuery did not propagate the withCredentials property to the native XmlHttpRequest.
The Obligatory Note on Internet Explorer
Internet Explorer 8 and 9 have limited support for CORS. Namely:
- Only GET and POST with a content type of plain/text are supported
- It does not support preflight
- No custom headers may be added to the request
- Credentialed requests are not supported
- Requests must be targeted to the same scheme as the hosting page
Internet Explorer 10 now has native support for CORS. However, as of this writing, IE 10 only supports credentialed requests between domains that have a matching second-level domain name, e.g., a.mydomain.com requesting to b.mydomain.com. It is not yet possible to send credentials between mydomain.com and otherdomain.com.
Testing
A great resource for testing CORS requests can be found at test.cors.org. This test site allows you to:
- Send CORS requests to a remote server to verify its capabilities
- Send CORS requests to a test server to explore CORS features
Alternatives to CORS
If your web application must run in browsers that do not support CORS or interact with servers that are not CORS-enabled, there are several alternatives to CORS that have been utilized to solve the cross-origin communication restriction.
- JSONP. This is a technique that exploits the HTML script element exception to the same-origin security policy. Script tags can load JavaScript from a different domain and query parameters can be added to the script URI to pass information to the server hosting the script about the resources that you wish to access. The JSONP server will return JavaScript that is evaluated in the browser that calls an agreed upon JavaScript function already on the page to pass server resource data into your page.
- OpenAjax Hub. This is an JavaScript Ajax library that allows integration of multiple client-side components within a single web application. Trusted and untrusted components to co-exist within the same page and communicate with each other as long as they all include the OpenAjax Hub JavaScript library. The framework provides a security manager to allow the application to set security policies on component messaging. Iframes are used to isolate components into secure sandboxes.
- easyXDM. This is a JavaScript library that allows for string-based cross domain communication via iframes. It works on the same principals as OpenAjax Hub but does not have the security manager component.
- Proxied Iframe. This do-it-yourself technique involves including an iframe on your page from the domain you wish to communicate with. This assumes that you are able to host pages on this other domain. The JavaScript running in the iframe serves as a rest proxy to the server containing the resources you wish to access. Communication between your application and the rest proxy will take place using post message. Post message is part of the HTML5 standard, but there is also a jQuery implementation for non HTML5-compliant browsers.
Have a comment? Leave it below.
Excellent article.
Question – is CORS support implemented by the web server or by the application server?
Thanks SS. Where server support for CORS is implemented is up to you.
It can be done in the web server via Apache mod_headers, for example, or in the application server via a Servlet filter or a Rails Controller before filter. There are several open source filter implementations available. Doing it in the application would give you the most flexibility.
Dude when did you get a blog?
Love the pic.
too many words for me to read though.
I have some problems with IE7 and some AJAX code, you should continue to provide support for IE7
Great resource for understanding CORS.
Could you also throw some light on CORS support in mobile web browsers? Is it supported on Android, iOS & Windows Phones? Are there any potential pitfalls of doing cross-domain on mobile browsers?
Excellent article Bob. I based some recent work with CORS off this document which you might be familiar with :). I’d just like to share my findings in getting cookies to propagate correctly during preflight:
Along with setting the withCredentials = true on the xhr, the OPTIONS response must also:
* Set the header Access-Control-Allow-Credentials: true.
* Set the Access-Control-Allow-Origin header to the Origin of the request. Using the star (*) will not work here.
HTH
Excellent idea…It’s really to see that you have to share such a helpful post.
really helpful article thank you! I have a question can we use JSONP ?
Yes, JSONP is a common workaround. Check out the Alternatives to CORS section at the end of the article.
thanks for sharing Bob, I’m using the following code for backbone to be able to use an API made in Ruby on Rails.
posts = new PostsCollection();
posts.fetch({
dataType: ‘jsonp’,
success : function (data) {
console.log(data);
}
});
If you are using rails you could use a gem called: ”rack-cors” and you can remove the jsonp datatype for you ajax call, regards
If you try http://terrasus.com/detail.jsp?articleID=396 article step by step it will work fine.
if you produce jsonp response you should get the callback value and set it to your response dynamically. This article has a detail explanation.
Hi,
Right now I am doing Cross-Domain $.ajax call with QueryString. But I want to do it for Cookies and Header.
My ajax call as below :
$.ajax(
{
url: rdWebURL,
headers: ssoHeader
}).success(function () {
window.open(rdWebURL, ‘_blank’);
});
Please help me for cookeis and Header.
Thanks for this very useful article.
If CORS is the way to go (it seems so, to me) is there a design pattern for attempting CORS transactions but if it fails silently fall back to using JSONP (when we know the server supports JSONP requests right now)? I’m wondering whether the decision to use CORS or an alternative can, in certain cases, be put off so that code written now can be made more robust (or in way, self-healing/self-improving) in anticipation of future developments.
(Part of the reason I ask is that I’m currently looking at some js extension code that runs in a browser served from server A. The code uses JSONP to get data from server B (different domain), all well and good. But it also retrieves data from server C (also different domain) using JSON (ie, without JSONP wrapping and callback fn). I don’t see any expicit CORS handing going on in the jQuery calls so I’m trying to figure out how this is happening. It made me think that if we need JSONP today there may come a day in the not distant future when we can dispense with it on the fly).
Couple things, your sudo code is not formatted properly. That JSONP link is spam…
Do you know if most mobile devices (smartphones, pads, etc.) can take advantage of CORS? I am using Web API 2.0 and have CORS working on laptops/PCs, but do not have my external sites setup so that I can test mobiles yet.
Nice post…
Thanks. CORS is supported by most mobile browsers. Take a look at this browser support matrix for more information: http://enable-cors.org/client.html
For those who don’t have server admin privileges, Yahoo’s YQL open proxy can marshal both HTML and XML across domains, see my example here:
https://gist.github.com/rickdog/d66a03d1e1e5959aa9b68869807791d5