I've been bitten by some nasty issues today in regards to using a domain cookie as part of my FormsAuthentication operations. In the app I'm currently working on we need to have single sign-on that spans multiple sub-domains (www.domain.com, store.domain.com, mail.domain.com etc.). That's what a domain cookie is meant for - when you set the cookie with a Domain value of the base domain the cookie stays valid for all sub-domains.

I've been testing the app for quite a while and everything is working great. Finally I get around to checking the app with Internet Explorer and I start discovering some problems - specifically on my local machine using localhost.

It appears that Internet Explorer (all versions) doesn't allow you to specify a domain of localhost, a local IP address or machine name. When you do, Internet Explorer simply ignores the cookie. In my last post I talked about some generic code I created to basically parse out the base domain from the current URL so a domain cookie would automatically used using this code:

private void IssueAuthTicket(UserState userState, bool rememberMe)
{
    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userState.UserId,
                                                         DateTime.Now, DateTime.Now.AddDays(10),
                                                         rememberMe, userState.ToString());
    
    string ticketString = FormsAuthentication.Encrypt(ticket);
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, ticketString);
    cookie.HttpOnly = true;
    
    if (rememberMe)
        cookie.Expires = DateTime.Now.AddDays(10);

    var domain = Request.Url.GetBaseDomain();
    if (domain != Request.Url.DnsSafeHost)
        cookie.Domain = domain;
HttpContext.Response.Cookies.Add(cookie); }

This code works fine on all browsers but Internet Explorer both locally and on full domains. And it also works fine for Internet Explorer with actual 'real' domains. However, this code fails silently for IE when the domain is localhost or any other local address. In that case Internet Explorer simply refuses to accept the cookie and fails to log in.

Argh! The end result is that the solution above trying to automatically parse the base domain won't work as local addresses end up failing.

Configuration Setting

Given this screwed up state of affairs, the best solution to handle this is a configuration setting. Forms Authentication actually has a domain key that can be set for FormsAuthentication so that's natural choice for the storing the domain name:

    <authentication mode="Forms">
      <forms loginUrl="~/Account/Login"
             name="gnc"
             domain="mydomain.com"
             slidingExpiration="true"
             timeout="30"
             xdt:Transform="Replace"/>
    </authentication>

Although I'm not actually letting FormsAuth set my cookie directly I can still access the domain name from the static FormsAuthentication.CookieDomain property, by changing the domain assignment code to:

if (!string.IsNullOrEmpty(FormsAuthentication.CookieDomain))
    cookie.Domain = FormsAuthentication.CookieDomain;

The key is to only set the domain when actually running on a full authority, and leaving the domain key blank on the local machine to avoid the local address debacle.

Note if you want to see this fail with IE, set the domain to domain="localhost" and watch in Fiddler what happens.

Logging Out

When specifying a domain key for a login it's also vitally important that that same domain key is used when logging out. Forms Authentication will do this automatically for you when the domain is set and you use FormsAuthentication.SignOut().

If you use an explicit Cookie to manage your logins or other persistant value, make sure that when you log out you also specify the domain. IOW, the expiring cookie you set for a 'logout' should match the same settings - name, path, domain - as the cookie you used to set the value.

HttpCookie cookie = new HttpCookie("gne", "");
cookie.Expires = DateTime.Now.AddDays(-5);

// make sure we use the same logic to release cookie
var domain = Request.Url.GetBaseDomain();
if (domain != Request.Url.DnsSafeHost)
    cookie.Domain = domain;

HttpContext.Response.Cookies.Add(cookie);

I managed to get my code to do what I needed it to, but man I'm getting so sick and tired of fixing IE only bugs. I spent most of the day today fixing a number of small IE layout bugs along with this issue which took a bit of time to trace down.