Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cross-domain cookies in IE 8 and 9 without an iframe?

Suppose I control two domains, www.api_domain.com and www.website_domain.com. www.api_domain.com offers an API that requires the user to authenticate and then uses a session cookie to recognise the user who is making requests. www.website_domain.com loads a script onto its pages from www.api_domain.com and that script wants to make calls to API URLs on www.api_domain.com with the current user's cookie and use the results in some way on the page from www.website_domain.com.

For initially loading the script, or for any API URLs that don't require the user's session cookie to work, the easiest solution is simply to use an

Access-Control-Allow-Origin: http://www.website_domain.com

header on the response from www.api_domain.com. This seems to work out of the box on all browsers besides IE, and although IE won't respect the Allow-Origin header on AJAX requests made using jQuery's AJAX methods, there are libraries like xdr.js which do some magic behind the scenes to make jQuery, IE and the Allow-Origin header play nice together and behave like in all other browsers (I don't know the details of what xdr.js does, but it works perfectly for non-credentialed requests as far as I can see).

The problem comes when I want to hit a URL on http://www.api_domain.com that requires the user's session cookie. When this problem is discussed in a browser-agnostic setting, two solutions are usually proposed:

  1. Use Access-Control-Allow-Credentials: true on the response from to make cookies be sent even with cross-domain requests.
  2. Create an iframe on the page on http://www.website_domain.com with origin http://www.api_domain.com, have the two windows communicate with each other using HTML5 post messages and delegate all responsibility for making requests to http://www.api_domain.com to the iframe.

I would greatly prefer to use option 1 if possible, since it lets you write your Javascript code to use the API on http://www.api_domain.com in the same way that you would write it to touch a same-domain API. To use the iframe approach, we'd need to learn or create some framework for sending AJAX-like requests to the iframe, with success and error handlers. It also means we need to create the code to be loaded into the iframe, which will just be a whole chunk of thin wrappers for hitting the API URLs. It just seems uglier, trickier, and harder to understand than the first approach.

However, I can't figure out how to make option 1 work on IE. I'm setting Access-Control-Allow-Credentials: true on my API URLs, and all other browsers send cookies to those URLs, but IE 9 doesn't, even with the xdr.js library. (I haven't tested on IE 8.) There are no other symptoms to report whatsoever. I can see the proper Access-Control-Allow-Origin and Access-Control-Allow-Credentials headers in the responses from www.api_domain.com when I view them in IE's developer tools, but there are no cookie headers in the request.

Is there some hack or magical incantation that I can use to make Internet Explorer respect the Access-Control-Allow-Credentials header, or some other header I can use that IE recognises?

like image 814
Mark Amery Avatar asked Mar 01 '13 09:03

Mark Amery


1 Answers

Option 1 is not possible in IE9 or below because there is no support for CORS using the XMLHttpRequest. Additionally, if you try to use XDomainRequest, you will never be able to send any cookies along with your request. I've been down this road several times working on writing a ui testing library to be used with testswarm. What you want to do is just not possible in that manner.

Here is a post by Eric Law, an ex-Microsoft developer, discussing the issue in detail: http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx

The relevant sections, which make clear that sending cookies with a CORS request is impossible in IE 8 and 9, are as follows:

In Internet Explorer 8, the XDomainRequest object was introduced. This object allows AJAX applications to make safe cross-origin requests directly by ensuring that HTTP Responses can only be read by the current page if the data source indicates that the response is public; in that way, the Same Origin Policy security guarantee is protected. Responses indicate their willingness to allow cross domain access by including the Access-Control-Allow-Origin HTTP response header with value *, or the exact origin of the calling page.

When designing the new object, ensuring that existing sites and services would not be put at risk was our top priority. To that end, we imposed a number of restrictions on what sort of requests can be made with the XDomainRequest object.

...

5: No authentication or cookies will be sent with the request

In order to prevent misuse of the user’s ambient authority (e.g. cookies, HTTP credentials, client certificates, etc), the request will be stripped of cookies and credentials and will ignore any authentication challenges or Set-Cookie directives in the HTTP response. XDomainRequests will not be sent on previously-authenticated connections, because some Windows authentication protocols (e.g. NTLM/Kerberos) are per-connection-based rather than per-request-based.

Sites that wish to perform authentication of the user for cross-origin requests can use explicit methods (e.g. tokens in the POST body or URL) to pass this authentication information without risking the user’s ambient authority.

Now assuming you control both locations, you could presumably create a server to server authentication process and go about passing a session id of sorts provided from the domain, for the other domain, of which the client is actually on through your request. It's not pretty, but it works. This method is also mentioned in the article. You will want to be careful though because it opens up the possibility for session hijacking.

like image 103
Michael K. Avatar answered Sep 19 '22 22:09

Michael K.