Future of Cross-Site Requests

By Anne van Kesteren

Introduction

Due to the adhoc way the Web evolved its security model is somewhat complex. The security model we have today is largely based around the origin concept (not to be confused with the relgion of the Ori). The origin is a tuple consisting of the scheme, host, and port of a given document on the Web. For requests where authors can somehow obtain the response data, browser vendors apply a same-origin policy. This means that such requests can only be targeted at documents that have the same origin as the one from which the request was initiated. This is true for XMLHttpRequest and XSLT for instance. On the other hand, the HTML img and script elements do not have this policy aplied, because although the image is displayed and the script is executed, the script author does not get access to the response data.

Currently cross-site requests happen by means of using a proxy server or a script element that fetches some JSON wrapped in a callback function. A proxy server has the disadvantage that you need an additional request and that you need to have access to such a server in the first place. In case of authenticated scenarios it also requires the user to share his authentication data with the site that initiates the request, which would be bad for security. The JSON scenario is bad because it allows the third party to execute arbitrary script on your domain and it also doesn't solve the problem for other formats, so no cross-site XSLT.

We want cross-site requests to enable the sharing resources. https://email.example/ and https://addressbook.example/ should be able to share data. Concretely, if a user allows Google to share his/her addressbook with Facebook, we want Facebook to be able to obtain that data without the user having to share his/her Google credentials with Facebook. We also want to use the same request APIs we use today, XMLHttpRequest, <?xml-stylesheet href="..."?>, <event-source src="...">, et cetera.

Access Control for Cross-Site Requests

The W3C has been working on cross-site requests for roughly two years now. This work has mostly been done in public on the public-appformats@w3.org mailing list of the Web Application Formats Working Group. The Access Control for Cross-Site Requests defines an abstract request and response model which protocols, such as XMLHttpRequest and <?xml-stylesheet href="..."?> can use to enable cross-site requests. Defining it in this way has the advantage that cross-site policies are unified across a set of APIs used in the Web platform. The other advantage is that apart it doesn't require the introduction of a new API on the client specifically designed to handle cross-site requests.

In essence the proposal distinguishes between two types of requests. On one hand, we have requests using the GET method and a set of headers that falls within a whitelist and on the other hand we have all other types of requests. The former is considered to be "safe" and the requesting can already be emulated using the img element for instance. The latter are "unsafe" and cannot be directly performed. First the server needs to acknowledge it is actually willing to deal with such an "unsafe" request.

"Safe" Requests

The only thing that matters for "safe" requests is whether the response data can be exposed or not. Since the current Web architecture already allows the request part to be performed using the img or script element what matters is whether the server is ok with exposing the response. The server could for instance allow cross-site requests from example.org using the following code:

Access-Control: allow <example.org>

In case of cross-site XSLT or (in the future) XBL it might be convenient to put this information directly into the XML file:

<?access-control allow="http://example.org"?>

A server can also allow requests from all domains using a wildcard. This can be convenient if you want to allow requests from everywhere or if the server doesn't want to expose its cross-site request policy and wants to implement it based on the mandatory Access-Control-Origin request header. That would make it:

Access-Control: allow <*>

"Unsafe" Requests

"Unsafe" requests are a tad more complicated. It is currently possible to use the form to initiate cross-site requests using the POST method, but servers that do strict Content-Type checking will not be vulnarable to cross-site XML payloads. Access Control used in conjunction with XMLHttpRequest would change that, so we need to introduce some kind of safeguard. In addition, enabling other HTTP methods to function properly is a requirement as it would be a shame if REST-style APIs were somehow prohibited.

The solution the Working Group eventually came up with is to have a preflight request that checks if the server is ok with the request about to come. In case of a positive response it basically means that the server is aware of cross-site requests and is prepared to deal with them. This preflight request is performed by the user agent and does again not affect the API the Web author needs to use to initiate the request. This preflight request uses the HTTP OPTIONS method and expects a response similar to a "safe" request (though it will not check the entity body in case of XML). If this response is positive the actual request with the desired HTTP method and request headers will be performed. For the response data of the actual request the same tactic as with "safe" requests is used to make sure that the server is not only ok with dealing with requests, but is also ok with sharing the data.

(The preflight request is slightly more complicated than explained here as there's also a way for preflight requests to be cached and a way to disable/cache preflight requests for a whole set of URIs. This is explained in detail in the specification.)

Applications

The expectation is that Access Control for Cross-Site Requests will be used by XMLHttpRequest Level 2, XBL 2.0, XSLT, and HTML5 server-sent events. This basically means that all those protocols will share the same API on the server to enable cross-site requests. The API used to initiate the request will be near identical (unless you are implementing).

Cross-Document Messaging

One scenario Access Control does not address is cross-domain communication between frames. For instance, example.org embedding a widget from Yahoo! using an iframe cannot communicate with that iframe other than by using fragment identifier hacks. Access Control is not an adequate mechnism to solve this problem as it is designed to deal with requests and responses, not for communication through script. HTML 5 has introduced a messaging API that addresses this case. Basically for a.example and b.example it could look like this:

// a.example
var windowB = frames[0]
windowB.postMessage("hi!", "http://b.example/") // windowB is b.example

// b.example
document.addEventListener("message", messageHandler, false)
function messageHandler(e) {
  if(e.origin == "a.example" && e.data == "hi!")
    // do something
}

Other Proposals

There have been two other noteworthy proposals for doing cross-site requests: JSONRequest and more recently XDomainRequest. These proposals both have issues given the use cases outlined thus far:

JSONRequest has the additional limitation that only JSON payloads are supported. And while XDomainRequest allows other formats this is not actually identified through the Content-Type header as that would introduce security issues. However, not telling what format is being transmitted leads to content sniffing which is a big security risk.

Given that Access Control for Cross-Site Requests does not suffer from these issues it seems that it is a better way forward.

Implementations

Access Control for Cross-Site Requests has no shipping implementation yet. It was going in Firefox 3, but because some details were not entirely clear during code freeze it was taken out to not deadlock the specification.

Cross-Document Messaging is and will be shipping in Firefox 3, Opera 9.5, Safari post-3.1, and Internet Explorer 8.