[转]cross-site xmlhttprequest with CORS
http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
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. TheCross-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, usingXMLHttpRequest
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, usingXDomainRequest
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 fromXMLHttpRequest
(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-siteXMLHttpRequest
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 onXMLHttpRequest
in keeping with the emerging XMLHttpRequest Level 2specification, 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:
Alternatively, you can also use the “in” operator:
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:
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’sXDomainRequest
object, but is supported by Firefox 3.5 and Safari 4 withXMLHttpRequest
. 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:
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’sXDomainRequest
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.
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.