Cross Site Origin Requests aka Cross Origin Resource Sharing

Everybody knows you can’t do AJAX requests to other domains .. well actually, you can! This where CORS (Cross Origin Resource Sharing) comes in.

Normally, when you try to do a cross domain request your browser will catch it and throw an Error instead. This restriction is voluntarily imposed by the browser, technically you could do it. You’re not allowed to because there is a possibility for abuse and security. Operating on a whitelist basis is easier. By sending the rights headers you can convince the browser that: yes indeed, I am allowed to make this request.

Before an AJAX request is made to another domain your browser will initiate a pre-flight OPTIONS request. This request is part of the HTTP standard (the foresight these guys had was amazing). If the reply is favorable (ie. the right headers are present) it will then proceed to do the actual AJAX request.

Lets say your webservice is located on webservice.com/api and you are calling from my.domain.com. The header you should emit on webservice.com/api is:

Access-Control-Allow-Origin: http://my.domain.com

Note: Keep in mind that http and https are considered to be different domains.

Note: If you are using https make sure you certificate is valid. That is, it’s signed and verified or you’ve added it to your local whitelist. If not, the pre-flight request will silently fail.

Implementation

The first thing you might think of is emitting a header that just whitelists everything:

    header("Access-Control-Allow-Origin: *");

But do you really want to allow everything? No, of course not. You want a whitelist:

    header("Access-Control-Allow-Origin: http://my.domain.com https://my.domain.com http://my.otherdomain.com");

But now anyone doing an OPTIONS request can see what domains we support, ie. our whole whitelist. And especially if the whitelist grows, the header will be very very long. We can do better!

Look at the domain that’s calling, and emit just that domain:


if ($_SERVER["REQUEST_TYPE"] === "OPTIONS") { // special CORS track
    $allowed_domains = array("http://my.domain.com", "https://my.domain.com", "http://my.otherdomain.com");
    $calling_domain = get_calling_domain($_SERVER);
    if (in_array($calling_domain, $allowed_domains)) {
        header("Access-Control-Allow-Origin: " . $calling_domain);
    }
    exit; // no need to do anything else for OPTIONS request
}

The browser just uses the OPTIONS request to check the capabilities of your server, you don’t actually need to return any content.

Headers

Another useful header is:

Access-Control-Allow-Headers: Content-Type, X-Custom-Header

This allows you to pass extra headers. The cross site AJAX requests are very limited by default. If you want to send even a simple Content-Type header (like application/json) you need to explicitly whitelist it. You can send any header you want, but you must whitelist it. Content-Type is one you’ll want if you’re doing something with JSON or content that isn’t text/plain.

Cookies

If you have authentication on your webservice you’ll need to send some cookies, or at least a session identifier. You must specify this as well.

To allow sending of cookies emit the header:

Access-Control-Allow-Credentials: true

Additionally in your XMLHTTPRequest (JavaScript, on the calling domain) you must set:

xml_http_request.withCredentials = true;

This will allow you to use sessions and cookies.

Note: these cookies are considered third party cookies. If your visitors have disabled third party cookies this approach wont work and you must first coax the user into allowing third party cookies on your domain.

CORS for XMLHTTPRequest works in the latest version of Firefox and Chrome and since Internet Explorer version 8. The old ActiveXObject and XDomainRequest don’t support extra headers and cookies. JSONp is a decent fallback in this case, but beware of the limitations.

Leave a comment