AJAX cross domain requests with CORS

A lot of people (including me a few weeks ago) still think that the same-origin policy of the XMLHttpRequest object makes it impossible to send an AJAX request to a foreign domain, but luckily this isn’t true anymore. Ways like JSONP, Flash bridges or some weird iframe calls (it’s fun – spent a whole night on that aproach ;P) have been found to tackle this issue somehow, but there’s a nice draft from the W3C called Cross-Origin Resource Sharing or short CORS, which solves this problem in a very elegant way and is supported by all major browsers.

The good thing is you don’t need to make any changes on the client side, everything will work like you’re expecting it from a usual XMLHttpRequest, all you’ve got do is to make the server send some “magic” HTTP headers and cross domain requests won’t be a problem no more. And that’s the bad thing as well: Sending the headers shouldn’t be a problem, but it implies that you have server-side access. However, let’s just assume you have access to the server and take a look on a simple example including some PHP and Node.js code snippets.

A simple request

Before we start digging deeper I want to show you the most simple cross domain request (XDR) you could do. As I’ve said nothing specific has to be done on the client side, so it’s just the usual JavaScript code you need for an AJAX request. Since we need different hosts for a cross domain request we assume the following code is part of the page http://www.example.com/dir/page.html:

var request = new XMLHttpRequest();
request.open('GET', 'http://xdr.example.net/', true);
request.send();

Normally the browser won’t allow such a request, since we try to communicate with another host, but with the right header this is no problem anymore. Here is a simple example for a possible HTTP response with CORS:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Length: 6
Content-Type: text/plain
Connection: keep-alive

foobar

The interesting part is the Access-Control-Allow-Origin header, which defines what origins are allowed to send requests. A star (*) means requests from any origin are allowed, any other value has to be identical to the request origin, otherwise the response will be discarded by the client (aka browser).

But what exactly is the origin of a request? That’s the scheme://host:port part of the URL the request came from (the :port part is optional). In our example the request came from http://www.example.com/dir/page.html, therefore the Access-Control-Allow-Origin value has to be either * or http://www.example.com to be accepted.

Coding the server response

Here are some examples how you could generate the response on the server side. Since this can become as complex as you like I’ll just show some short PHP and Node.js snippets here, but I hope some other guys will give you hints in the comments about how to send the HTTP response in other languages.

PHP: The HTTP response of the example above could be generated by an index.php with the following code:

<?php
header('Access-Control-Allow-Origin: *');
echo 'foobar';
?>

Node.js: A simple HTTP server which always sends a response similar to the one in the example could look like this:

function pickup(request, response) {
	response.setHeader('Access-Control-Allow-Origin', '*');
	response.end('foobar');
}
require('http').createServer(pickup).listen(80);

Preflighted requests

The example above will work for GET, HEAD and POST requests without any user-defined headers, but as soon as you have set some additional headers via request.setRequestHeader() or want to use another HTTP method, request.send() will actually result in two requests: a preflight and the real one. The browser will handle the whole preflight stuff for you, so you only have add some extra code on the server side.

A preflight uses two special headers, Access-Control-Request-Method and Access-Control-Request-Headers, to tell the server, what is going to be sent in the actual request. The response contains two similar headers, Access-Control-Allow-Methods and Access-Control-Allow-Headers, to tell the client what methods and headers are allowed for the intended request.

If the preflight fails due to an error (HTTP status 404, 500 or something like that) or non-matching Access-Control-Request and Access-Control-Allow headers, the whole request will fail. However, we’re optimistic, so here is an example of a successful XMLHttpRequest with preflight:

var request = new XMLHttpRequest();
request.open('GET', 'http://xdr.example.net/', true);
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.send();

Once again the usual AJAX JavaScript, but this time we’ve send a non-standard header along with our request. Therefore the client will automatically send a preflight before the request and the server has to send an approbate response:

HTTP/1.1 200 OK
Date: Wed, 21 Dec 2011 14:40:58 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, PUT, DELETE
Access-Control-Allow-Headers: X-Requested-With, Content-Type
Content-Length: 0
Content-Type: text/plain

As you can see it is possible to define a list of allowed methods and headers by separating each with a comma. After the preflight has been completed the client will send the actual request and the server has to respond the same way as in the simple request example:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Length: 6
Content-Type: text/plain
Connection: keep-alive

foobar

As you can see Access-Control-Allow-Origin is the only header that has to be included in every response, but it’s no problem to include the other headers as well.

The server code to handle preflights

We only have to add some lines to recognize and respond to preflight requests to the code we already know. Fortunately this is quite simple, since a preflight always uses the HTTP method OPTIONS:

PHP: The method used by the HTTP request is stored in the global $_SERVER['REQUEST_METHOD'] variable:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
	header('Access-Control-Allow-Origin: *');
	header('Access-Control-Allow-Methods: POST, GET, PUT, DELETE');
	header('Access-Control-Allow-Headers: X-Requested-With, Content-Type');
}
else {
	header('Access-Control-Allow-Origin: *');
	echo 'foobar';
}
?>

Node.js: The method property of the request object contains the name of the used HTTP method:

function pickup(request, response) {
	if (request.method == 'OPTIONS') {
		response.setHeader('Access-Control-Allow-Origin', '*');
		response.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE');
		response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
		response.end();
	}
	else {
		response.setHeader('Access-Control-Allow-Origin', '*');
		response.end('foobar');
	}
}
require('http').createServer(pickup).listen(80);

Preflight caching

In order to reduce the network traffic the client won’t send another preflight request as long as it holds a valid preflight-response in its cache. However, this could make debugging somehow difficult, so I recommend to disable caching during development.

This can be achived by sending an Access-Control-Max-Age header along with the preflight-response, which defines how many seconds the response is allowed to stay in the preflight-result-cache of the client. Setting the header value to zero will disable caching.

Some words on the Internet Explorer

Once again our special blue-e’d friend needs some extra care. The Internet Explorer supports CORS since version 8, but alas someone decided not to implement it the standard way, so we can’t use the XMLHttpRequest object, but have to create a XDomainRequest (MSDN page) object instead. The properties and methods of XDomainRequest are very similar to the normal XMLHttpRequest object – except for two things: It won’t allow us to use other methods like GET or POST or to set any user defined headers, so it’s rather useless when you want to communicate with a RESTful API. However, here’s some JavaScript code that should send a cross domain request in any browser that supports CORS:

var request = XDomainRequest ? new XDomainRequest() : new XMLHttpRequest();
request.open('GET', 'http://xdr.example.net/', true);
request.send();

Finally IE10 supports all CORS features within the normal XMLHttpRequest object, but I think we still have to wait a while until version 10 has become the normal use-case…

Further reading

That’s pretty much everything you need to know for a start. You might also want to check out the W3C Cross-Origin Resource Sharing draft for all details and the “CORS enabled” page in the W3C Wiki to see who’s already in the boat. The MDN page on HTTP access control is another good explanation of CORS and my Node.js CORS example server might be of some use, too.

5 thoughts on “AJAX cross domain requests with CORS”

  1. Cross domain is entirely a different subject. But cross sub-domain is relatively easy. All you need to do is to set the document.domain to be same in both the parent page and the iframe page.

Leave a Reply

Your email address will not be published. Required fields are marked *