CORS Gotchas
CORS stands for Cross-Origin Resource Sharing and represents a method of accessing/sharing resources across domains. These resources can be anything from web fonts to APIs. The CORS standard is only implemented in browsers, since this is the only place where it makes sense. While this is the preferred method of sharing resources, it has a few rough edges.
The Basics
In the following we’ll refer to the response headers, since the request headers are automatically set by the browser.
A special header defined in the CORS specification must be sent by the server in each response to allow access to a resource:
Access-Control-Allow-Origin: allowed-domain.com
To allow any domain to access resources the header can be set to a wildcard:
Access-Control-Allow-Origin: *
This is the bare minimum and it will suffice for sharing static resources such as web fonts. But once we throw in authentication, which is needed by most APIs, things get messy.
Authentication
Authentication, with either cookies or HTTP auth, requires an additional response header:
Access-Control-Allow-Credentials: true
Also the browser needs to send cookies/credentials in the request, so for example in JavaScript we need to pass an additional parameter to XHR object:
var req = new XMLHttpRequest();
req.open('GET', url);
req.withCredentials = true;
req.onreadystatechange = handler;
req.send();
A few notes on authentication:
- We cannot use domain wildcards for the
Access-Control-Allow-Origin
header when allowing credentials. The domain must be explicitly stated. This is a feature to prevent CSRF. - Credentials are not allowed with synchronous requests in some browsers, since these requests cannot be pre-flighted.
- IE and Opera were slow to adopt CORS; only IE 10 and Opera 12 support it properly.
Pre-flighted requests
Some cross-origin requests are pre-flighted, meaning that an OPTIONS
request is automatically sent by the browser before each request, to query the server for allowed HTTP methods, allowed headers or to check whether credentials are allowed.
OPTIONS
requests are only sent if any of the following conditions are met:
- The request method is other than
GET
orPOST
. - The request Content-Type is other than
application/x-www-form-urlencoded
,multipart/form-data
ortext/plain
. - Custom headers are set.
Obviously, sending OPTIONS
before each real request has a bit of overhead. Fortunately, there is a header that tells the browser to cache these requests:
Access-Control-Max-Age: 86400
Allowing methods and headers
In order to create RESTful APIs we need to use more than the GET
and POST
verbs.
Servers can inform the clients of the allowed HTTP methods by using another type of CORS header:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Any number of HTTP verbs can be listed here.
Allowed headers can be set with:
Access-Control-Allow-Headers: Origin, X-Reqested-With, Accept
X-Reqested-With
is needed for asynchronous requests.
All these headers except Access-Control-Allow-Origin
are commonly included only in the pre-flight response.
IE support
As always, IE chooses to hit developers with a crowbar.
Instead of extending XMLHttpRequest
, IE 8 introduces another interface named XDomainRequest
, with a similar interface, which supports CORS, but not its full set of features.
Luckily IE 10 fixes this and adds support for CORS within the standard XMLHttpRequest.