cross-site xmlhttprequest with CORS

Editor’s Note: This article sure is a popular one! The Fetch API is now available in browsers and makes cross-origin requests easier than ever. Check out this Hacks post or the link above to learn more.

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 <a href="https://developer.mozilla.org/En/XMLHttpRequest">XMLHttpRequest</a> 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 <a href="http://msdn.microsoft.com/en-us/library/cc288060%28VS.85%29.aspx">XDomainRequest</a> 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

About Arun Ranganathan

More articles by Arun Ranganathan…


46 comments

  1. William Edney

    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

    July 6th, 2009 at 16:41

  2. William Edney

    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

    July 6th, 2009 at 16:48

  3. thinsoldier

    Been waiting all day an update!

    :) thanks.

    July 6th, 2009 at 17:05

  4. Jeff Walden

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

    July 6th, 2009 at 19:38

  5. Gen Kanai

    @Jeff- thanks for the edit. Updated.

    July 6th, 2009 at 20:55

  6. Arun Ranganathan

    @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 :)

    July 6th, 2009 at 23:15

  7. Firefox Fanatic

    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?

    July 7th, 2009 at 08:35

  8. Arun Ranganathan

    @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

    July 7th, 2009 at 09:42

  9. William Edney

    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

    July 7th, 2009 at 10:53

  10. William Edney

    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

    July 7th, 2009 at 11:23

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

    July 8th, 2009 at 06:30

  12. […] 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 […]

    July 8th, 2009 at 23:06

  13. […] 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 […]

    July 8th, 2009 at 23:20

  14. […] 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 […]

    July 9th, 2009 at 00:01

  15. […] 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 […]

    July 9th, 2009 at 02:54

  16. […] 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 […]

    July 20th, 2009 at 12:07

  17. […] 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 […]

    August 27th, 2009 at 14:31

  18. Xiaoxin

    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.

    August 31st, 2009 at 09:01

  19. […] 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. […]

    November 9th, 2009 at 02:53

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

    December 2nd, 2009 at 07:05

  21. […] Mozilla team suggests in their post about CORS that you should check for the existence of the withCredentials property to determine if the browser […]

    May 25th, 2010 at 18:37

  22. Nathan Friedly

    I always get back null or “” when calling getAllResponseHeaders() & getResponseHeader() on cross-domain requests. Why is that and how can I read the headers?

    May 26th, 2010 at 13:50

  23. […] Cross-Origin Resource Sharing 어플리케이션을 하나의 사이트에서 제공하며 데이터를 다른 곳으로 출판할 수 있다. […]

    July 20th, 2010 at 06:41

  24. WebDAV

    We have tested CORS in Firefox 3.6, Chrome 5 and Safari 5 and found that only Chrome can handle requests to servers with authentication properly. We have tested cross-domain PROPFIND request with Basic, Digest and NTLM and found that Firefox supports only Digest authentication (for PROPFIND it does not support Basic even with SSL for some reason) while Safari does not support any authentication for PROPFIND requests at all.

    It is a great disappointment as PROPFIND and other WebDAV verbs are critical for our product, hope they will fix it.

    We have published the results here: http://www.webdavsystem.com/ajaxfilebrowser/programming/cross_domain. See Cross-Domain Requests with Authentication section at the bottom of the page.

    July 29th, 2010 at 08:43

    1. Christopher Blizzard

      Do you have a test case for this? It should work.

      August 16th, 2010 at 14:59

      1. Vladimir Lichman

        Christopher, we have posted a bug here:
        https://bugzilla.mozilla.org/show_bug.cgi?id=597301

        There is a detailed description about how to reproduce it.

        September 16th, 2010 at 19:34

  25. Intekhab

    Hi Arun,

    Your article is very helpful to understand the concept of Cross domain calling. I tried out the same but when i call a web service (WCF with webHttpbinding) hosted on other machineanother wb site i got an error 403 forbidden with status 0 and ready state 4.

    Actual scenario:
    WCF with Httpbinding
    Ajax call using XMLHTTP object
    POST method
    Preflight request as content type is application/Json

    Any help is appriciated

    Intekhab

    November 16th, 2010 at 10:30

  26. kn33ch41

    If anyone is having trouble sending cookies with withCredentials, remember that Access-Control-Allow-Origin must have a valid domain specified that corresponds to those cookies; a wildcard will not work.

    Also, for anyone sending files asynchronously with XHR2, bear in mind that Chrome sets a Content-Type header by default when sending a base64-encoded stream, for example, which must be specified as an allowed header in the server’s preflighted Access-Control-Allow-Headers response. Firefox, however, does not need this specified.

    December 15th, 2010 at 06:13

  27. Nizzy

    With CORS, why getAllResponseHeaders() return null?

    any idea?

    thank you
    Nizzy

    December 30th, 2010 at 00:44

  28. […] Robust Software : Cross-site XMLHttpRequest with CORS ; […]

    January 27th, 2011 at 03:31

  29. Demetris

    Your said “The web developer does not need to worry about the mechanics of preflighting, since the implementation handles that”. Are you referring to the client side (the browser) that automatically generates the preflight request? I think so. Is this also always true about the server? I do know Jetty has a configuration to handle preflight requests but most other cases i have been the preflight response is handled by a user defined servlet. What do you think?

    Thanks

    January 29th, 2011 at 08:13

  30. Demetris

    Also – I intercepted the CORS preflight request with a local agent, inspected the OPTIONS headers and then returned the response as it should be (headers to allow the origin etc. with rn terminating them). Nothing happens on the browser – why is that the case? Using Chrome on Android.

    January 29th, 2011 at 08:18

  31. […] trying to configure Apache to act as a proxy to a remote server, to allow cross-domain AJAX using CORS. To achieve this, I need Apache to respond to 2 HTTP verbs, like […]

    February 14th, 2011 at 10:24

  32. […] CORS Have started working on mobile stuff at work (via PhoneGap Build and Jo) and recently started using XHR for login within the app. PhoneGap enables this somehow via CORS (this is my understanding, please correct if wrong) which allows for Cross Origin Resource Sharing through the exchange of headers listing trusted origins etc. Really just got a brief understanding of it out of curiosity. […]

    April 10th, 2011 at 03:52

  33. […] you don’t care about some browsers (i.e. != Firefox 3.5, Safari 4, Chrome 2), you could add a CORS response header in the form of Access-Control-Allow-Origin: *. But then again, if you have control […]

    May 20th, 2011 at 15:57

  34. Totti Anh Nguyen

    Thanks for the clear Javascript sample snippet to demo the feature !

    May 25th, 2011 at 03:39

  35. […] an API with no JSONP support, the cross-domain barrier can quickly become a formidable one. CORS is slowly becoming a viable alternative, but it requires that the remote service support it via […]

    August 2nd, 2011 at 08:03

  36. Max

    Thanks for the info! This may be what I’m looking for:

    I have an HTTP page that needs to perform and AJAX POST to a secure url. Both on the same domain.
    Will CORS allow me to do that?

    I also have total control on the JS that is loaded by the page, so I can even host the JS files in a secure (HTTPS) environment too.

    What do you think?

    October 19th, 2011 at 11:34

  37. santhosh kumar

    I face the same cross domain issue… I am sure whether I am setting the header properly

    http://stackoverflow.com/questions/7747695/cross-domain-issue-xmlhttp

    Am I missing something?

    October 19th, 2011 at 16:50

  38. Zain Shaikh

    I tried the sample provided in firefox 3.0.1.
    http://arunranga.com/examples/access-control/preflightInvocation.html

    but I am getting this error:

    Access to restricted URI denied” code: “1012

    any solution???

    December 2nd, 2011 at 06:22

  39. […] Mozilla team suggests in their post about CORS that you should check for the existence of the withCredentials property to determine if the browser […]

    December 14th, 2011 at 06:59

  40. Ranjan

    Hi Arun,

    I tried your sample file, it works fine.
    However, when I try the same with different URL on our server ,it doesn’t work.

    Could you please tell me why it is not working.

    Ranjan

    June 20th, 2012 at 06:19

  41. Kristoffer S

    This one saved my day. Thanks!

    October 10th, 2012 at 01:44

  42. kishore

    When i use Cross Domain XMLHTTP request, it works find in Fire Fox. But the same code shows 403 Forbidden in chrome.
    Can any body please suggest me how to resolve this issue ?

    October 29th, 2012 at 03:21

  43. Hridya

    Hi,

    I tried your code to hit my webservice. It worked in chrome and IE.

    Bur Firefox gave me a 405 Method Not Allowed error.

    Can you please help me on this?

    Thanks in advance

    March 7th, 2013 at 00:09

  44. Nathan Friedly

    Hridya: That probably means Firefox is preflighting your requestion with an OPTIONS request and your web server doesn’t support those. You can remove the preflighting by not adding cookies (withCredentials=false) and not setting any headers.

    March 7th, 2013 at 16:24

Comments are closed for this article.