Thursday, 28 February 2013

Create shared cookie across sub domains (asp.net)

Let's say you have www.domain.com which writes a cookie. You need that cookie to be accessible by a sub domain of your original site: other.domain.com. To enable this, the cookie that is written must not include the specific sub domain in the domain property of the cookie (www). The cookie's domain must be sub domain-less, and begin with a period (.). This then permits the browser to include the cookie in requests to all sub domains, permitting each sub domain to read the cookie content.

Basically, the cookie's domain must go
From this: www.domain.com
To this: .domain.com

The most obvious way to achieve this is to hard code the domain value when writing the cookie - but that always feels wrong and it doesn't help when running on localhost. Nor is it desirable if you want to change your domain.

The answer I came up with is an extension method that makes use of the requested url. That way, you don't ever have to worry what domain you're running under: you'll always get just the the sub domain safe version. It also takes care of a locally hosted domain. Enjoy.



Edit 1: Ed has pointed out the comments that asp.net will read the most explicit cookie first. So to be wary of implementing this approach in a site with already existing cookies that do include the sub domain.

Edit 2: Be wary of this approach when using an integrated cloud hosting provider such as AppHarbor. AH Applications are given a url that varies only in the sub domain on the main AppHarbor domain. E.g the application FooBar on AppHarbor is hosted by default on foobar.apphb.com. Using the technique above would allow any other site hosted in AppHarbor so read the client cookie! To mitigate this you can apply your own hostname to the application and make it canonical so that your site cannot be accessed from the original url. Indeed, its also a good reason not to put sensitive information in the cookie!

8 comments:

  1. Nice clean example.

    One thing to be careful of here - asp.net will always take the most explicit cookie value first (e.g. on the site 'www.domain.com', a cookie for 'www.domain.com' will be considered more relevant than '.domain.com').

    This won't be an issue on fresh sites that always implement this method, but be mindful if converting from a site you already have established - so if you were running with 'www.domain.com' with existing users and then implement this code going forward with both the 'www' and 'other' version your users could end up with two cookies which may have some big implications.

    Also remember to change the value in your web.config for any session based cookies written by asp.net out of your control.

    ReplyDelete
  2. Thanks Ed, good to know! Will update the post. What's the web.config setting you mention? Sounds out of scope if you're just writing your own cookies.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Oops, HTML tags messed up my previous post:

    Yeah, only relevant for session cookies

    <system.web>
    <httpCookies domain=".domain.com"/>
    </system.web>

    Got there in the end!

    ReplyDelete
  6. This line: if (context.Request.IsLocal)

    Is Supposed to be if (!context.Request.IsLocal)

    ReplyDelete
  7. Also: "by design domain names must have at least two dots otherwise browser will say they are invalid"
    http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain

    You don't want to mess with the domain localhost, asp.net has this figured out and you will be able to access cookie across localhost websites already


    //asp.net correctly handles localhost so all
    //major browsers will accept cookie
    if (!context.Request.IsLocal)
    {
    var domainSegments = context.Request.Url.Host.Split('.');
    domain = "." + String.Join(".", domainSegments.Skip(1));
    }


    var cookie = new HttpCookie(name, value)
    {
    if(!string.IsNullOrEmpty(domain)
    {
    Domain = domain
    }
    };

    ReplyDelete
    Replies
    1. Thanks sweetog for pointing out those errors, I've updated the gist.

      Delete