Ian Schmitz
Ian Schmitz

Reputation: 330

ASP.NET cross subdomain cookies not being attached to request

I've been trying to build a local proof of concept to move over our current solution to a different URL structure. I'm using ASP.NET with 3 projects that currently has these URLs mapped to them:

mysite.com
mysite.com/api
mysite.com/app

To setup the proof of concept I've setup 3 sites locally in IIS with the following URLs:

mysite.com
api.mysite.com
app.mysite.com

And have added the following entries into the HOSTS file:

127.0.0.1 mysite.com
127.0.0.1 app.mysite.com
127.0.0.1 api.mysite.com

Currently app.mysite.com talks to api.mysite.com to perform a user login, which returns a cookie back in the response. The issue is that the cookie is not being stored under mysite.com. Subsequent requests to api.mysite.com don't have the cookie attached in the request header, and therefore fail.

I've experimented setting the cookie's domain property with no success, as well as not including a domain property.

An example of a cookie returned in the request:

Set-Cookie: MyCookie=somestuff; domain=.mysite.com; expires=Sat, 06-Sep-2014 00:02:04 GMT; path=/; HttpOnly

Yet the cookie is never attached to any requests to api.mysite.com nor can i see it in the cookie browser of Chrome, Firefox, IE etc...

Note that I've enabled CORS in web.config to enable cross domain requests.

EDIT: In response to Owain's answer. I'll clarify my current setup a little more.

Regarding <machineKey> I have created a machineKey and used the same values on both applications in the web.config file. This was already working locally and in production when using mysite.com/api and mysite.com/app It wasn't till moving to subdomains that i ran into this issue.

Here is my code for creating and attaching the cookie:

        private void EncryptAndAttachCookieToHeaders(FormsAuthenticationTicket ticket)
        {

            string encryptedTicket = FormsAuthentication.Encrypt(ticket);

            HttpCookie newCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            newCookie.Domain = ".mysite.com";
            newCookie.Expires = DateTime.Now.AddMonths(3);
            newCookie.HttpOnly = true;
            newCookie.Secure = false; 

            System.Web.HttpContext.Current.Response.Cookies.Add(newCookie);

        // For testing purposes
        HttpCookie hc = new HttpCookie("cookie1", "value");
        hc.Domain = ".mysite.com"; 
        hc.Expires = DateTime.Now.AddMonths(3);
        HttpContext.Current.Response.Cookies.Add(hc);

        HttpCookie hd = new HttpCookie("cookie2", "value");
        hd.Domain = ".api.mysite.com"; 
        hd.Expires = DateTime.Now.AddMonths(3);
        HttpContext.Current.Response.Cookies.Add(hd);

All of these cookies (real one plus the two tests) are visible when viewing the response in Fiddler. However subsequent requests to api.mysite.com do NOT have any cookies attached in the request header. The browser doesn't seem to want to store the cookie now that I've moved to the subdomain structure.

Upvotes: 0

Views: 1900

Answers (2)

Ian Schmitz
Ian Schmitz

Reputation: 330

I found out the answer. The issue actually resided in how the AJAX request was being formed. I had to add an AJAX property called withCredentials to allow the browser to send the cross domain cookie. A drawback to this approach is that IE 9 and older don't support it....

From post Setting a cookie on a subdomain from an ajax request :

Set the allow Credentials header on api

Access-Control-Allow-Credentials: true

Use withCredentials for the request

$.ajax({
    url: a_cross_domain_url,
    xhrFields: { 
        withCredentials: true 
    }
});

Otherwise the XMLHttpRequest will not send the cookies, regardless of the Access-Control-Allow-Credentials header.

Remove the wildcard on Access-Control-Allow-Origin

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

The wildcard * will not work. The browser will discard the response if withCredentials was set.

Upvotes: 0

Owain van Brakel
Owain van Brakel

Reputation: 3349

To make cross domain login work you first need to edit the <machineKey> element inside the web.config.

By default it is something like this

<machineKey
    validationKey="AutoGenerate,IsolateApps"
    decryptionKey="AutoGenerate,IsolateApps"
    validation="SHA1"
    decryption="Auto" />

With these settings, the .NET framework uses the automatically generated validationKey and decrytionKey

With the default settings the validation key and the decryption key will get generated by ASP.NET and will get used for authentication ticket and cookie, if you want multiple applications to share the same authentication ticket and cookie, you just need to set the validationKey and decrytionKey in all the applications to the same values

Make sure they are the same in both application something like for example

<machineKey
    validationKey="FBFF3D4EFD359FD58AA480E0A63A9C817463A30EF5AFF4E212AD3321C122AECFFEC427C26D24B67296F5EBBB6A3736BF37A5027718E5426B92C9AC606F9AD66F"
    decryptionKey="048F8A9F3D6A7D2E88738B36BEEB85FF0B4E23EEF11976D1C0F6F03B91CCFC37"
    validation="SHA1"
    decryption="AES" />

You can easily create some keys on this site http://aspnetresources.com/tools/machineKey

To make the authentication cookie visible at all other sub domains, you'll need to modify the cookie’s domain attribute.

// Call SetAuthCookie method
FormsAuthentication.SetAuthCookie("Test account", false);

//modify the Domain attribute
System.Web.HttpCookie TestCookie = 
       System.Web.Security.FormsAuthentication.GetAuthCookie(User.Identity.Name.ToString(), false);

TestCookie.Domain = "mysite.com"; // (Also try ".mysite.com" i'm unsure about this one) the second level domain name
Response.AppendCookie(TestCookie);

This should be everything you need to make cross domain cookies work

Upvotes: 1

Related Questions