There’s no doubt that Ajax is one of the most exciting, useful, and necessary web technologies available to front-end developers. Unfortunately, it’s also one of the most restrictive — especially when it comes to gathering content from other domains. Web developers are nothing if not persistent, so we’ve come up with a variety of ways to get around cross-origin restrictions, including JSONP, server-side proxies made with PHP, ProxyPass
proxying, Flash transports, creative iFrame uses, and more. What many developers don’t know is that there’s a W3C specification called Cross-Origin Resource Sharing, or CORS, which provides a standard for cross-origin Ajax requests with minimal hassle.
Since the Ajax API is different across browsers, and most developers use a JavaScript toolkit, examples within this post will use the MooTools JavaScript framework. CORS will work with any JavaScript framework, since the core philosophy and code is configured on the server, not the client side. Due to its popularity, Apache will be used in server-side configuration examples.
Basic Ajax Requests
Each of the JavaScript frameworks abstracts XMLHttpRequest
objects (or in the case of Internet Explorer, ActiveXObject
or Microsoft.XMLHttp
) to make Ajax requests. These requests feature a URL and may contain extra headers, data, different request types (GET
, POST
, PUT
, or DELETE
), and much more. A basic Ajax request would look something like this:
// Create a new Ajax request
var request = new Request.JSON({
// The URL to get content from
url: "/countries.json",
// The success callback
onSuccess: function(countries) {
// Log out the content
console.log("The countries are: ", countries);
}
}).send(); // Send the request
The URL within the request above is local; countries.json
is located within the same origin. What if we try to get tweets from Twitter, though?
// Create a new Ajax request
var request = new Request.JSON({
// The URL to get content from
url: "http://twitter.com/statuses/user_timeline/davidwalshblog.json",
// The success callback
onSuccess: function(tweets) {
// Log out the content
console.log("The tweets are: ", content);
}
}).send(); // Send the request
The request to Twitter will fail because the request destination, twitter.com
, is not the same as the origin. Each browser provides its own error message; Chrome will warn you with: XMLHttpRequest cannot load http://twitter.com/statuses/user_timeline/davidwalshblog.json. Origin http://davidwalsh.name is not allowed by Access-Control-Allow-Origin.
In the case of Twitter, we could use a JSONP request instead, but the problem with JSONP is that the destination server must support it. Even if the destination supports JSONP, you cannot POST
to the URL or send specified request headers. How do we fix this conundrum? CORS, of course.
Implementing CORS
CORS allows for cross-origin requests with little fuss. Since the destination server is the entity in control and “at risk,” it must be configured with the proper headers and security settings. A few of the key headers include:
Access-Control-Allow-Origin
- A specific URI or
*
which identifies what domain(s) may make cross-origin requests to this (destination) server. (*
is an undesirable configuration value, since it allows any and all origins to make requests to your server.) Access-Control-Allow-Methods
- A comma-separated list of allowed request methods.
Access-Control-Allow-Headers
- A comma-separated list of allowed request headers.
Assuming that a web site is hosted on an Apache server, the virtual host could be configured as follows:
<VirtualHost *:80>
DocumentRoot "/path/to/website/root"
ServerName domain.tld
Header set Access-Control-Allow-Origin http://example.com
Header set Access-Control-Allow-Methods POST,GET
Header set Access-Control-Allow-Headers X-Authorization,X-Requested-With
</VirtualHost>
The configuration above only allows remote requests from http://example.com
, the request type may only be POST
or GET
, and allowed headers are X-Authorization
and X-Requested-With
.
If you want to allow anyone to make Ajax requests to your domain and with any request type, you could opt for this configuration:
<VirtualHost *:80>
DocumentRoot "/path/to/website/root"
ServerName domain.tld
Header set Access-Control-Allow-Origin *
Header set Access-Control-Allow-Methods POST,GET,DELETE,PUT,OPTIONS
Header set Access-Control-Allow-Headers X-Authorization,X-Requested-With
</VirtualHost>
With the destination server configured, we can send cross-origin requests from wherever we want:
// Create a new Ajax request
var request = new Request({
// The URL to get content from
url: "http://example.org/content.php",
// The success callback
onSuccess: function(content) {
// Log out the content
console.log("The content is: ", content); // Works!
}
}).send(); // Send the request
The big question is, “which browsers have implemented the CORS spec?” Most of them, actually. Firefox 3.5+, Safari 4+, Chrome 3+, and IE9+. IE8 doesn’t support standard CORS, but it does have an XDomainRequest
object, and Opera has not yet implemented CORS. That current level of support does, however, make CORS a viable option for many web sites. If you manage a web service or simply want to tinker with Ajax strategies, keep CORS in mind; I have a suspicion that CORS will play a large role in the future of web development.
Developer Gift
I’m terrible when it comes to figuring out what other people want as a gift. One gift that never misses is a good bottle of wine or a hard-to-find beer. Sometimes, a smooth wine or hoppy beer fuels a great, late-night coding session. Other times, it helps me get away from the text editor and relax. One added benefit of choosing wine is that your spouse might more quickly forgive your all-night hacking sins. My favorite wines come from Kendall Jackson.