Using CORS for Cross-Domain Ajax Requests

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.

Comments

  1. SS says:

    Excellent article.

    Question – is CORS support implemented by the web server or by the application server?

    • Bob Czarnecki says:

      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.

  2. Ricks EvilTwin says:

    Dude when did you get a blog?
    Love the pic.
    too many words for me to read though.

  3. recetas says:

    I have some problems with IE7 and some AJAX code, you should continue to provide support for IE7

  4. Dayson Pais says:

    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?

  5. jhannus says:

    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

  6. Excellent idea…It’s really to see that you have to share such a helpful post.

  7. Burçlar says:

    really helpful article thank you! I have a question can we use JSONP ?

  8. 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

  9. Highdown says:

    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…

  10. Bob Czarnecki says:

    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

Leave a Comment