CORS for XHR in IE10

The fourth platform of IE10 simplifies building cross-site scenarios that work consistently across browsers by supporting Cross-Origin Resource Sharing (CORS) for XMLHttpRequest (XHR). CORS for XHR makes sharing data across sites simple and flexible. In the most basic scenario CORS enables creating data sources accessible from any site, and with a few small tweaks you can choose to constrain allowed sites, support data modification, and even allow authentication. Most importantly CORS keeps existing sites secure by requiring server participation.

Simple Cross-Origin XHR

Let’s look at how a cross-origin XHR request compares to a same-origin request. From script, the only difference is the URL passed to the open method. For example, say we’re working on a script that fetches a list of photo albums.

Traditional XHR

// Script running on https://photos.contoso.com

var xhr = new XMLHttpRequest();

xhr.onerror = _handleError;

xhr.onload = _handleLoad;

xhr.open("GET", "/albums", true);

xhr.send();

Now we want to access the list of albums from another origin. The other origin can be a completely different domain or a different host with the same base domain. Either way, just pointing at the full URL from another site is enough to get the browser to automatically send a CORS request.

CORS-Enabled XHR

// Script running on https://www.contoso.com

var xhr = new XMLHttpRequest();

xhr.onerror = _handleError;

xhr.onload = _handleLoad;

xhr.open("GET", "https://photos.contoso.com/albums", true);

xhr.send();

Sites can provide fallback for older browsers by wrapping this in feature detection. Checking for “withCredentials” is the best approach since it directly relates to CORS support for XHR.

CORS-Enabled XHR with Feature Detection

// Script running on https://www.contoso.com

var xhr = new XMLHttpRequest();

if ("withCredentials" in xhr) {

xhr.onerror = _handleError;

xhr.onload = _handleLoad;

xhr.open("GET", "https://photos.contoso.com/albums", true);

xhr.send();

} else {

// Fallback behavior for browsers without CORS for XHR

}

At this point our client code makes a CORS request directly to "https://photos.contoso.com", but the request fails to return any data. The failure occurs because the server isn’t participating yet. Taking a quick look at the developer tools gives us an idea what went wrong.

Screenshot showing the F12 tools indicating no 'Access-Control-Allow-Origin' header was found.

Here we can see the server needs to send an “Access-Control-Allow-Origin” header in the response. In our scenario we’re not opening up our albums for any site to access, but want to enable access solely from “https://www.contoso.com”. Doing this requires allowing the server to identify where the request originated. Examining our outgoing request reveals a new header containing precisely this information, “Origin”.

Simple CORS Request Headers

GET https://photos.contoso.com/albums HTTP/1.1

Origin: https://www.contoso.com

...

Using this information the server can choose to limit access to any set of sites. If the server always adds an “Access-Control-Allow-Origin” header with a value of '*' then all sites will have access. For our scenario, we’ll have the server verify the origin and then set “Access-Control-Allow-Origin” to allow only “https://www.contoso.com”.

Simple CORS Response Headers

HTTP/1.1 200 OK

Access-Control-Allow-Origin: https://www.contoso.com

...

With the above updates in place, our “https://www.contoso.com” client can now access album lists from the server at “https://photos.contoso.com”.

Cross-Origin XHR with Preflight

The “simple” CORS requests discussed so far are great for basic, read-only scenarios, like downloading a photo album. Taking the next step by modifying data across sites requires a bit more work on the server. For example, say we’re adding code in the client to create a new album.

var xhr = new XMLHttpRequest();

xhr.onerror = _handleError;

xhr.onload = _handleLoad;

xhr.open("PUT", "https://photos.contoso.com/albums", true);

xhr.send(JSON.stringify({ name: "New Album" }));

Running this as-is doesn’t work. Examining the network traffic reveals a request is sent, but not the one we expected.

Screenshot of the F12 tools showing an OPTIONS preflight request.

What the browser actually sent is known as a preflight request. Preflight requests are sent before requests that may result in data modification on the server. Such requests are identified by the presence of non-simple properties as defined in the CORS specification. These properties range from certain HTTP methods like “PUT” to custom HTTP headers. Browsers send preflight requests to ask the server for permission to send the actual request. In our example the browser is verifying a “PUT” request is allowed.

Preflight Request

OPTIONS https://photos.contoso.com/albums HTTP/1.1

Origin: https://www.contoso.com

Access-Control-Request-Method: PUT

...

Getting the browser to send the actual request requires some changes on the server. Once again we can take a look at the developer tools for more information.

Screenshot showing the F12 tools indicating no 'Access-Control-Allow-Methods' list was found.

The first step is to make the server recognize the “OPTIONS” preflight request as distinct from other requests for the same URL. After the server verifies the preflight request by ensuring “Access-Control-Request-Method” is asking for “PUT” from an allowed origin, it sends the appropriate approval via the “Access-Control-Allow-Methods” header.

Preflight Response

HTTP/1.1 200 OK

Access-Control-Allow-Origin: https://www.contoso.com

Access-Control-Allow-Methods: PUT

...

Once preflight is out of the way and approved the actual request takes place.

Actual Request

PUT https://photos.contoso.com/albums HTTP/1.1

Origin: https://www.contoso.com

...

Adding the album is technically complete at this point, but our client code won’t know that unless the server responds correctly. Specifically the server must still include “Access-Control-Allow-Origin” in the response.

Actual Response

HTTP/1.1 200 OK

Access-Control-Allow-Origin: https://www.contoso.com

...

With that the client can add a new album cross-origin and recognize whether or not the action completed successfully.

Next Steps

Pairing CORS with other new platform features enables interesting scenarios. One example is the Cross-Site Upload Test Drive which tracks cross-origin file uploads using CORS, XHR, FileAPI, and progress events.

—Tony Ross, Program Manager, Internet Explorer