xmlHttp.getResponseHeader + Not working for CORS

user2039547 picture user2039547 · Feb 4, 2013 · Viewed 19.5k times · Source

I have an asp.NET WCF on .NET 4. This service is used to authenticate users. We are submitting a username and password and then an HTTP Header should be returned with the authentication cookie included. Using a locally hosted test page this is working correctly. I am now attempting to access the header information cross domain. I have installed my test page on a different machine and configured to call across to the WCF. The call is working and the 'data' reply in the call is correct. However, I am unable to access the header information with either of the following:

alert(xmlHttp.getAllResponseHeaders());

or

alert(xmlHttp.getResponseHeader("Set-Cookie"));

Using the debugger in IE and the 'Live HTTP Header' plugin for Firefox, I can see the header information is being returned.

In my global ajax page I am setting the response to handle CORS.

private void EnableCrossDomainAjaxCall()
{
    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");


    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {

        HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");

        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
        HttpContext.Current.Response.End();
    }

}

This is the AJAX I am using to call the service:

$("#btnLogin").click(function(e) {
    var geturl;
    geturl = $.ajax({
        // type: "POST",
        type: "GET",
        contentType: "application/json; charset=utf-8",
        url: 'http://10.0.4.66/AuthenticationService.svc/Login?Name=test&password=pwsd',
        // url: '../SecurityServer/AuthenticationService.svc/Login?Name=test&password=pwsd',
        dataType: "jsonp",
        error: function(request, status, error) {
            alert('Error Occured');
        },
        crossdomain: true,
        success: function(data, textStatus, xmlHttp) {
            // alert(xmlHttp.getResponseHeader("Content-Type"));
            document.write(xmlHttp.getResponseHeader("Content-Type") + "<br/>");
            alert(xmlHttp.getAllResponseHeaders());
            alert(xmlHttp.getResponseHeader("Set-Cookie"));
            var headers = '';
            var headerPair = xmlHttp.getAllResponseHeaders('wcfCookie').split("\r\n");
            var output = '';
            $.each(headerPair, function(key, line) {
                var parts = line.split(':');

                if (parts[0] == 'wcfCookie') {
                  ChocChip = parts[1]
                  return false
                }

            });
        }
    });

Below is my header information grabbed from 'Live HTTP headers'

Date: Mon, 04 Feb 2013 12:12:40 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 4.0.30319
Access-Control-Allow-Origin: *
Set-Cookie: wcfCookie=8D38D5D6A0F138FEB595DD016F7694EDDF3E6757C82ED3D419F5047A5294974C1885487465CEC0A0BCC2B3802C7B03FF9F5370A05D4CCBDDDABCB1558C3816044BF4F78209BF38C6B1A7CAD34CD3C85C40B8515CFB1C2B2694BC78803D8DACB4
Content-Length: 65
Cache-Control: application/json; charset=utf-8
Content-Type: application/x-javascript

Answer

apsillers picture apsillers · Feb 4, 2013

First, a little background:

You are using Access-Control-Allow-Headers, which specifies which request headers the client is allowed to send, but you are not specifying which response headers the client is allowed to read. To allow the client to read non-simple response headers, you need to use Access-Control-Expose-Headers. From the HTML5 Rocks CORS page:

During a CORS request, the getResponseHeader() method can only access simple response headers. Simple response headers are defined as follows:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

If you want clients to be able to access other headers, you have to use the Access-Control-Expose-Headers header. The value of this header is a comma-delimited list of response headers you want to expose to the client.

So, given that new information, you might do:

HttpContext.Current.Response.AddHeader("Access-Control-Expose-Headers", "Set-Cookie");

...but there's more to it than that.

Now, the actual answer:

There's one more serious problem here: the XHR specification explictily disallows reading Set-Cookie. That's because this is functionally a cross-domain cookie-stealing attack.

Suppose domain A makes a cross-domain request to domain B. When domain B sets cookies, it's setting domain-specific cookies for domain B only. Any attempt by domain A to read domain B's cookies is a violation of the same-origin policy for cookie access.

I don't know WCF, so I'm not the sure of best way to actually do what you want, but I'd guess the solution might be to pass an auth token not through cookies (e.g., a X-WCF-Auth header?) that domain A reads and then sets its own cookie.