50
votes

I've noticed that there are some real inconsistencies between browsers in terms of cookies.

This is going to be rather long so bear with me.

Note: I've setup a domain in my host file called "testdomain.com", this bug WONT work when using "localhost".

Note2: I am curious to know how this works on Apache/PHP if when you retrieve a cookie by name if it gives a collection of cookies back.

Wikipedia

Wikipedia states that: http://en.wikipedia.org/wiki/HTTP_cookie#Domain_and_Path

Domain and Path
The cookie domain and path define the scope of the cookie—they tell the browser that cookies should only be sent back to the server for the given domain and path. If not specified, they default to the domain and path of the object that was requested.

So if we push down:

Response.Cookies.Add(new HttpCookie("Banana", "2")
{

});

We should get a cookie with the domain used being the domain from the requested object, in this case it should be "testdomain.com".

W3

W3 states in the specification for cookies: http://www.w3.org/Protocols/rfc2109/rfc2109

Domain=domain

Optional. The Domain attribute specifies the domain for which the cookie is valid. An explicitly specified domain must always start with a dot.

So if we push down:

Response.Cookies.Add(new HttpCookie("Banana", "1")
{
    Domain = Request.Url.Host
});

We pushed down the host-name explicitly, we should get a domain name set on the cookie which would be prefixed with the dot, in this case it should be ".testdomain.com".

It also states what's on Wikipedia:

Domain Defaults to the request-host. (Note that there is no dot at the beginning of request-host.)


With me so far?

If I use the first method, defining a Domain:

Response.Cookies.Add(new HttpCookie("Banana", "1")
{
    Domain = Request.Url.Host
});

This is the results:

IE9: 1 cookie

IE with 1 cookie and domain explicitly set

Opera: 1 cookie

Opera with 1 cookie and domain explicitly set

Firefox: 1 cookie

Firefox with 1 cookie and domain explicitly set

Chrome: 1 cookie

Chrome with 1 cookie and domain explicitly set

As you can see, both Opera and IE both set an EXPLICIT domain without the dot prefix.

Both Firefox and Chrome DO set the EXPLICIT domain with a dot prefix.

If I use the following code:

Response.Cookies.Add(new HttpCookie("Banana", "2")
{

});

IE / Opera: Both have the exact same result, the domain WITHOUT the dot prefix.

Funnily enough, Firefox and Chrome both create cookies WITHOUT the dot prefix.

(I cleared all cookies and ran the code again)

Firefox:

Firefox with 1 cookie and domain explicitly set

Chrome:

Chrome with 1 cookie and domain explicitly set

INTERESTING BIT

This is where it gets interesting. If I write the cookies one after another like so:

Response.Cookies.Add(new HttpCookie("Banana", "1")
{
    Domain = Request.Url.Host
});
Response.Cookies.Add(new HttpCookie("Banana", "2")
{

});

PERSONALLY I would expect one cookie to exist in the browser, because I assume it's based on the cookie name.

Here's what i've observed:

In IE / Opera, the LAST cookie set is the cookie that is used. This is because the Cookie name and Domain name are identical.

If you explicitly define a domain name with a dot, both browser will still see 1 cookie, the last cookie of the same name.

Chrome and Firefox on the other hand, see more than 1 cookie:

I wrote the following JavaScript to dump the values to the page:

<script type="text/javascript">

(function () {
    var cookies = document.cookie.split(';');
    var output = "";

    for (var i = 0; i < cookies.length; i++) {
        output += "<li>Name " + cookies[i].split('=')[0];
        output += " - Value " + cookies[i].split('=')[1] + "</li>";
    }

    document.write("<ul>" + output + "</ul>");
})();

</script>

These are the results:

IE - 2 cookies set (browser sees 1):

IE - 2 cookies set, the outcome

Opera - 2 cookies set (browser sees 1):

enter image description here

Firefox - 2 cookies set and browser sees 2!:

enter image description here

Chrome - 2 cookies set and browser sees 2!:

enter image description here


Now you're probably wondering wtf all this is.

Well:

  1. When you access the cookie by Name in C#, it gives you 1 cookie. (the first cookie that has that name)
  2. The browser sends ALL cookies to the server
  3. The browser doesn't send any information other than the key/value of the cookie. (this means the server doesn't care about the domain)
  4. You can access both cookies of the same name, if you retrieve them by index

The problem...

We had to change our Authentication to specify the domain in the cookie when we pushed it down.

This broke Chrome and Firefox, users were no longer able to login, because the server would try authenticate the old auth cookie. This is because (from my understanding) it uses the Authentication Cookie Name to retrieve the cookie.

Even tho there are two cookies, the first one is retrieved which happens to be the old one, authentication fails, user isn't logged in. SOMETIMES the correct cookie is first in the list, and the authentication succeeds...

Initially we solved this by pushing a cookie with the old domain to expire it. This worked in Chrome and Firefox.

But it now broke IE/Opera since both browsers don't care about the domain and only compare the cookie based on the name.

My conclusion is that the domain on a cookie is a complete utter waste of time.

Assuming that we must specify the domain, and we can't rely on users to clear their browser cache. How can we resolve this problem?

Update:

Digging into how .NET signs a user out.

if (FormsAuthentication._CookieDomain != null)
{
    httpCookie.Domain = FormsAuthentication._CookieDomain;
}

It looks like it's entirely possible for the Forms authentication to push an expired Auth cookie, that is entirely unrelated to the cookie the user is authenticated with. It doesn't use the current Auth Cookie's domain.

Which it can't use anyway, since the domain isn't pushed back to the server with the cookie.

Update 2

It seems FormsAuthentication is really broken. If you use an explicit domain name on a cookie when you authenticate the user, wait for the session to timeout, then refresh the page, the method of generating the cookie used by FormsAuthentication results in the domain being null which causes the browser to assign a dotless domain.

It requires that Forms be assigned a domain up front for it to be assigned to the cookie, this breaks a multi-tenant system...

2
This probably should be split into self-answered question describing in detail possible problems, and workarounds in answer. Good research.Petr Abdulin
It's not answered yet, @PetrAbdulin. Seems to me that expiring the old cookie using the explicit domain should be fine in IE/Opera (I've had similar issues mind you but cookies only lasted a few days anyway)Buildstarted
This is stunning. I just wasted a whole morning trying to understand why Firefox added the prefixing dot to the cookie domain. So frustrating!EvilDr
You can prevent the prefixed dot by setting Cookie.Domain = "", but Firefox STILL sends it to other sub-domains.EvilDr

2 Answers

10
votes

@WilliamBZA's suggestion helped solve the initial problem, but then signout/session timeout bug that results in the cookie creating an implicit domain cookie has made me come to the conclusion that the solution is...

Don't use Explicit cookies in .NET... ever

There are far too many problems, sure they can be solved by being explicit on the Form/Domain, Cookie/Domain, etc. To ensure that the correct domain is used everywhere. But if your application hosts multiple domains or is multi tenant, then it just becomes too problematic.

Lesson is learnt. Don't use explicit cookies.

5
votes

Can't help with why the cookies are treated differently, but a quick fix would be to use a different cookie name per sub-application rather than using the domain of the cookie.

In the case of Forms Authentication, change the name of the ASPXAUTH cookie.