XMLHttpRequest is used within many Ajax libraries, but till the release of browsers such as Firefox 3.5 and Safari 4 has only been usable within the framework of the same-origin policy for JavaScript. This meant that a web application using XMLHttpRequest could only make HTTP requests to the domain it was loaded from, and not to other domains. Developers expressed the desire to safely evolve capabilities such as XMLHttpRequest to make cross-site requests, for better, safer mash-ups within web applications. The Cross-Origin Resource Sharing (CORS) specification consists of a simple header exchange between client-and-server, and is used by IE8’s proprietary XDomainRequest object as well as by XMLHttpRequest in browsers such as Firefox 3.5 and Safari 4 to make cross-site requests. These browsers make it possible to make asynchronous HTTP calls within script to other domains, provided the resources being retrieved are returned with the appropriate CORS headers.

A Quick Overview of CORS

Firefox 3.5 and Safari 4 implement the CORS specification, using XMLHttpRequest as an “API container” that sends and receives the appropriate headers on behalf of the web developer, thus allowing cross-site requests. IE8 implements part of the CORS specification, using XDomainRequest as a similar “API container” for CORS, enabling simple cross-site GET and POST requests. Notably, these browsers send the ORIGIN header, which provides the scheme (http:// or https://) and the domain of the page that is making the cross-site request. Server developers have to ensure that they send the right headers back, notably the Access-Control-Allow-Origin header for the ORIGIN in question (or ” * ” for all domains, if the resource is public) .

The CORS standard works by adding new HTTP headers that allow servers to serve resources to permitted origin domains. Browsers support these headers and enforce the restrictions they establish. Additionally, for HTTP request methods that can cause side-effects on user data (in particular, for HTTP methods other than GET, or for POST usage with certain MIME types), the specification mandates that browsers “preflight” the request, soliciting supported methods from the server with an HTTP OPTIONS request header, and then, upon “approval” from the server, sending the actual request with the actual HTTP request method. Servers can also notify clients whether “credentials” (including Cookies and HTTP Authentication data) should be sent with requests.

Capability Detection

XMLHttpRequest can make cross-site requests in Firefox 3.5 and in Safari 4; cross-site requests in previous versions of these browsers will fail. It is always possible to try to initiate the cross-site request first, and if it fails, to conclude that the browser in question cannot handle cross-site requests from XMLHttpRequest (based on handling failure conditions or exceptions, e.g. not getting a 200 status code back). In Firefox 3.5 and Safari 4, a cross-site XMLHttpRequest will not successfully obtain the resource if the server doesn’t provide the appropriate CORS headers (notably the Access-Control-Allow-Origin header) back with the resource, although the request will go through. And in older browsers, an attempt to make a cross-site XMLHttpRequest will simply fail (a request won’t be sent at all).

Both Safari 4 and Firefox 3.5 provide the withCredentials property on XMLHttpRequest in keeping with the emerging XMLHttpRequest Level 2 specification, and this can be used to detect an XMLHttpRequest object that implements CORS (and thus allows cross-site requests). This allows for a convenient “object detection” mechanism:

if (XMLHttpRequest)
{
    var request = new XMLHttpRequest();
    if (request.withCredentials !== undefined)
    {
      // make cross-site requests
    }
}

Alternatively, you can also use the “in” operator:

if("withCredentials" in request)
{
  // make cross-site requests
}

Thus, the withCredentials property can be used in the context of capability detection. We’ll discuss the use of “withCredentials” as a means to send Cookies and HTTP-Auth data to sites later on in this article.

“Simple” Requests using GET or POST

IE8, Safari 4, and Firefox 3.5 allow simple GET and POST cross-site requests. “Simple” requests don’t set custom headers, and the request body only uses plain text (namely, the text/plain Content-Type).

Let us assume the following code snippet is served from a page on site http://foo.example and is making a call to http://bar.other:

 
var url = "http://bar.other/publicNotaries/"
if(XMLHttpRequest)
{
  var request = new XMLHttpRequest();
  if("withCredentials" in request)
  {
   // Firefox 3.5 and Safari 4
   request.open('GET', url, true);
   request.onreadystatechange = handler;
   request.send();
  }
  else if (XDomainRequest)
  {
   // IE8
   var xdr = new XDomainRequest();
   xdr.open("get", url);
   xdr.send();
 
   // handle XDR responses -- not shown here :-)
  }
 
 // This version of XHR does not support CORS  
 // Handle accordingly
}

Firefox 3.5, IE8, and Safari 4 take care of sending and receiving the right headers. Here is the Simple Request example. It is also instructive to look at the headers sent back by the server. Notably, amongst the other request headers, the browser would send the following in order to enable the simple request above:

GET /publicNotaries/ HTTP/1.1
Referer: http://foo.example/notary-mashup/
Origin: http://foo.example

Note the use of the “Origin” HTTP header that is part of the CORS specification.

And, amongst the other response headers, the server at http://bar.other would include:

Access-Control-Allow-Origin: http://foo.example
Content-Type: application/xml
......

A more complete treatment of CORS and XMLHttpRequest can be found here, on the Mozilla Developer Wiki.

“Preflighted” Request

The CORS specification mandates that requests that use methods other than POST or GET, or that use custom headers, or request bodies other than text/plain, are preflighted. A preflighted request first sends the OPTIONS header to the resource on the other domain, to check and see if the actual request is safe to send. This capability is currently not supported by IE8’s XDomainRequest object, but is supported by Firefox 3.5 and Safari 4 with XMLHttpRequest. The web developer does not need to worry about the mechanics of preflighting, since the implementation handles that.

The code snippet below shows code from a web page on http://foo.example calling a resource on http://bar.other. For simplicity, we leave out the section on object and capability detection, since we’ve covered that already:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '
Arun';
function callOtherDomain(){
if(invocation)
{
    invocation.open('POST', url, true);
    invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
    invocation.setRequestHeader('Content-Type', 'application/xml');
    invocation.onreadystatechange = handler;
    invocation.send(body);
}

You can see this example in action here. Looking at the header exchange between client and server is really instructive. A more detailed treatment of this can be found on the Mozilla Developer Wiki.

In this case, before Firefox 3.5 sends the request, it first uses the OPTIONS header:

OPTIONS /resources/post-here/ HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

Then, amongst the other response headers, the server responds with:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://arunranga.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000

At which point, the actual response is sent:

POST /resources/post-here/ HTTP/1.1
...
Content-Type: application/xml; charset=UTF-8
X-PINGOTHER: pingpong
...

Credentialed Requests

By default, “credentials” such as Cookies and HTTP Auth information are not sent in cross-site requests using XMLHttpRequest. In order to send them, you have to set the withCredentials property of the XMLHttpRequest object. This is a new property introduced in Firefox 3.5 and Safari 4. IE8’s XDomainRequest object does not have this capability.

Again, let us assume some JavaScript on a page on http://foo.example wishes to call a resource on http://bar.other and send Cookies with the request, such that the response is cognizant of Cookies the user may have acquired.

var request = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
  if(request)
  {
   request.open('GET', url, true);
   request.withCredentials = "true";
   request.onreadystatechange = handler;
   request.send();
  }

Note that withCredentials is false (and NOT set) by default. The header exchange is similar to the case of of a simple GET request, with the exception that now an HTTP Cookie header is sent with the request header. You can see this sample in action here.

A Note on Security

In general, data requested from a remote site should be treated as untrusted. Executing JavaScript code retrieved from a third-party site without first determining its validity is NOT recommended. Server administrators should be careful about leaking private data, and should judiciously determine that resources can be called in a cross-site manner.

References

20 comments

Post a comment
  1. William Edney wrote on July 6th, 2009 at 4:41 pm:

    Arun -

    Thanks for the excellent example. I grabbed the ‘Simple Example’ page, saved it to my file system, reloaded that page into another window using the ‘file:///’ URL and tried to invoke the cross-site query. This failed in both Firefox 3.5 (Mac) and Safari 4 (Mac).

    Is there some reason this isn’t working? Given that the W3 defines ‘file:///’ URLs as a valid origin, this should work IMHO.

    Cheers,

    - Bill

    Reply

  2. William Edney wrote on July 6th, 2009 at 4:48 pm:

    Arun -

    Ok, so maybe I’m being an idiot and your server is only authorizing the domain that you’re calling from in your example and not ‘*’.

    Hmmm…

    Cheers,

    - Bill

    Reply

  3. thinsoldier wrote on July 6th, 2009 at 5:05 pm:

    Been waiting all day an update!
    :) thanks.

    Reply

  4. Jeff Walden wrote on July 6th, 2009 at 7:38 pm:

    Typo: “Cross-Origin Resource Sharing”, not “request” sharing.

    Reply

  5. Gen Kanai wrote on July 6th, 2009 at 8:55 pm:

    @Jeff- thanks for the edit. Updated.

    Reply

  6. Arun Ranganathan wrote on July 6th, 2009 at 11:15 pm:

    @Bill — good question :) What’s happening when you take the simple request and run it locally (from file:///) is that the value of the Origin header is now null (“Origin: null”). But, my server-side PHP script doesn’t handle a null Origin and thus doesn’t send back the right response.

    @Jeff — yikes, good catch :)

    Reply

  7. Firefox Fanatic wrote on July 7th, 2009 at 8:35 am:

    So, Firefox 3.5, Safari 4, and Google Chrome 2 all support the CORS specification, while Internet Explorer 8 partially supports it with XDomainRequest. What about Opera? Is there any news on when they will support this functionality?

    Reply

  8. Arun Ranganathan wrote on July 7th, 2009 at 9:42 am:

    @FirefoxFanatic — no comment from Opera yet; the last public-facing message we got from an Opera engineer was: http://lists.w3.org/Archives/Public/public-webapps/2009AprJun/1223.html

    Reply

  9. William Edney wrote on July 7th, 2009 at 10:53 am:

    Arun -

    Did a bit more sussing of what’s going here.

    1. I’m an idiot and only after posting did I figure out that your server wasn’t configured with ‘Access-Control-Allow-Origin: *’.

    2. I found a test server that does:

    http://rockstarapps.com/test.php.

    3. Tested both FF 3.5 and Safari 4.X against that server. It turns out that Safari 4.X works properly, FF 3.5 does not. Off to Bugzilla…

    Thanks!

    Cheers,

    - Bill

    Reply

  10. William Edney wrote on July 7th, 2009 at 11:23 am:

    Arun -

    One last message. In reducing this for a testcase for FF 3.5, I found an error in my previous test. FF 3.5 works fine. Haven’t tried this in IE8, yet :-)

    Thanks again for these helpful examples :-) .

    Cheers,

    - Bill

    Reply

  11. Pingback from links for 2009-07-08 | 小李贼 on July 8th, 2009 at 6:30 am:

    [...] cross-site xmlhttprequest with CORS 站点间的 xmlhttp 交互 (tags: javascript ajax) [...]

    Reply

  12. Pingback from Ajax cross-domain con Mozilla 3.5 y Safari 4 | aNieto2K on July 8th, 2009 at 11:06 pm:

    [...] y ahora Firefox 3.5, ya implementan dicha mejora y nos permite trabajar con ella. Por otro lado Microsoft, en otro mundo, desarrolla XDomainRequest() que permite realizar [...]

    Reply

  13. Pingback from Ajax cross-domain con Mozilla 3.5 y Safari 4 : Blogografia on July 8th, 2009 at 11:20 pm:

    [...] y ahora Firefox 3.5, ya implementan dicha mejora y nos permite trabajar con ella. Por otro lado Microsoft, en otro mundo, desarrolla XDomainRequest() que permite realizar [...]

    Reply

  14. Pingback from Ajax cross-domain con Safari 4, Google Chrome 2 y Firefox 3.5 : Blogografia on July 9th, 2009 at 12:01 am:

    [...] Google Chrome 2 y ahora Firefox 3.5, ya implementan dicha mejora y nos permite trabajar con ella. Por otro lado Microsoft, en otro mundo, desarrolla XDomainRequest() que permite realizar [...]

    Reply

  15. Pingback from El Blog Mojaina » Ajax cross-domain con Safari 4, Google Chrome 2 y Firefox 3.5 on July 9th, 2009 at 2:54 am:

    [...] permitiendo una mejor integración entre servicios online. Safari4, Google Chrome 2 y ahora Firefox 3.5, ya implementan dicha mejora y nos permite trabajar con ella. Por otro lado Microsoft, en otro mundo, desarrolla XDomainRequest() que permite realizar [...]

    Reply

  16. Pingback from securitybananas.com » XMLHTTPReqest “Ping” Sweeping in Firefox 3.5+ on July 20th, 2009 at 12:07 pm:

    [...] brought my attention to the new Firefox 3.5+ CORS (Cross-Origin Resource Sharing) which is a way to do a cross domain XMLHTTPReqest. Does that sound scary? Well, it is, but [...]

    Reply

  17. Pingback from Chris B. – idéias e pensamentos » XMLHttpRequest cross-site: mais uma vez o IE fora do padrão on August 27th, 2009 at 2:31 pm:

    [...] não é falar da implementação em si, até porque eu não conseguiri escrever nada melhor do que foi escrito pelo hacks.mozilla, mas sim falar que novamente a Microsoft atrapalha os padrões (aliás, vejam com cuidado esse post [...]

    Reply

  18. Xiaoxin wrote on August 31st, 2009 at 9:01 am:

    Tested CORS with Chrome and it works however xhr.withCredentials always comes back undefined making this feature detection method unrealiable. Tested on Chrome 2.0.172.43.

    Reply

  19. Pingback from 5 years of Firefox at hacks.mozilla.org on November 9th, 2009 at 2:53 am:

    [...] One thing that’s become obvious over the last five years is the wide gap that’s emerging between the field of modern browsers – Firefox, Safari, Opera and Chrome – with the world’s most popular browser – IE. The modern browser is built for the future of web applications – super fast JavaScript, modern CSS, HTML5, support for the various web-apps standards, downloadable font support, offline application support, raw graphics through canvas and WebGL, native video, advanced XHR capabilities mixed with new security tools and network capabilities. [...]

    Reply

  20. Pingback from Cross Origin Resource Sharing – AKA The Holy Grail | Brit Gardner ::: Web Developer ::: Dallas, TX on December 2nd, 2009 at 7:05 am:

    [...] I stumbled across this article on the excellent Mozilla Hacks blog. Cross Origin Resource Sharing (CORS). Sweet! Finally a true [...]

    Reply

Add your comment