9

I have a web app can be installed on lots of domains and paths.

So:

  • client1Name.{mySite.com}
  • client2Name.{mySite.com}
  • demo.{mySite.com}/prospect1Name
  • demo.{mySite.com}/prospect2Name
  • demo.{mySite.com}/prospect3Name

All separate application instances of the same code.

The problem is that if a client logs in to client1Name.{mySite.com} then visits one of the other sites their browser will send the authentication cookie.

In all cases FormsAuthentication.SetAuthCookie doesn't set either the Path or the Domain.

What I would expect is:

  • client1Name.{mySite.com} - Domain = client1Name.{mySite.com} Path = /
  • client2Name.{mySite.com} - Domain = client2Name.{mySite.com} Path = /
  • demo.{mySite.com}/prospect1Name - Domain = demo.{mySite.com} Path = /prospect1Name
  • demo.{mySite.com}/prospect2Name - Domain = demo.{mySite.com} Path = /prospect2Name
  • demo.{mySite.com}/prospect3Name - Domain = demo.{mySite.com} Path = /prospect3Name

I can manually override .Net's behaviour to explicitly set these, but I'm not sure why I should need to - sure this should be the default behaviour when setting an authentication cookie or at least an option that can be set without re-writing big chunks of it.

Am I missing something? Is there some way to make FormsAuthentication.SetAuthCookie set the Path and Domain?

If not what is the best way to dynamically read the best Path and Domain? The same code has to run on all sites and I don't want to add a further configuration key.

Update

Here is my current solution:

// replacement for FormsAuthentication.SetAuthCookie(user.UserName, false);
// as that fails to limit the cookie by domain & path and fails.

var cookie = FormsAuthentication.GetAuthCookie(username, false);
cookie.HttpOnly = true;
cookie.Path = this.Request.ApplicationPath;
cookie.Secure = string.Equals("https", this.Request.Url.Scheme, StringComparison.OrdinalIgnoreCase);

// the browser will ignore the cookie if there are fewer than two dots
// see cookie spec - http://curl.haxx.se/rfc/cookie_spec.html
if (this.Request.Url.Host.Split('.').Length > 2)
{
    // by default the domain will be the host, so www.site.com will get site.com
    // this may be a problem if we have clientA.site.com and clientB.site.com
    // the following line will force the full domain name
    cookie.Domain = this.Request.Url.Host;
}

this.Response.Cookies.Add(cookie);

However, that seems like a lot of workaround for something FormsAuthentication.SetAuthCookie should be able to do. Is this really the best way?

Keith
  • 150,284
  • 78
  • 298
  • 434
  • Why would you expect these things in the cookie? My expectation is that the domain would be `{mySite.com}` and the path would be `/` always. – Joel Etherton Jul 15 '11 at 13:09
  • @Joel Etherton - whether it's the default behaviour or not, it doesn't seem like a very odd thing to have _{mySite.com}/client1Name_ and _{mySite.com}/client2Name_ not share authentication tickets. I shouldn't have to replace the whole method to do this. – Keith Jul 15 '11 at 15:28
  • Are these folder separations full application separations as well? – Joel Etherton Jul 15 '11 at 15:29

2 Answers2

9

I've had to do a lot of digging, but is looks like the reason FormsAuthentication.SetAuthCookie doesn't support this is because it shouldn't - IIS should never set paths on authentication cookies, and here's why...

Cookie paths are case-sensitive, so:

  • http://site/path
  • http://site/PATH

Are 2 different cookies for the browser - none of them (IE, FX, Safari, Opera or Chrome) will send /PATH's cookie to /path or vice versa.

IIS is case-insensitive, but will always reset the URL to the ASP application name's case.

This means that if the IIS application is called "PATH" and the user goes to http://site/path then they will be redirected to log-on at http://site/PATH/LogOn?ReturnUrl=/path by IIS/ASP.Net

After a successful log-on the user gets redirected back to the ReturnUrl specified, so:

  1. User goes to http://site/path
  2. Gets sent to http://site/PATH/LogOn?ReturnUrl=/path by IIS
  3. Enters log-on details and submits
  4. Response sets the cookie to /PATH and the location to /path (as defined by ReturnUrl)
  5. Redirected back to http://site/path
  6. Browser doesn't recognise /path, it only has a cookie for /PATH and so sends nothing!
  7. No cookie sent to application, so it serves a redirect back to http://site/PATH/LogOn?ReturnUrl=/path
  8. Go to step 2 and repeat.

This creates a problem for users if they have http://site/path as the URL for the application they will never appear to be able to log-on.

Further to this if they're already logged on to http://site/PATH and get sent a URL, say an email to a http://site/path/resource/id, they will get asked to log on all over again and won't be able to get to the new path.

This means that unless you need /PATH and /path to be completely different sites (unlikely outside certain UNIX only environments) you should never set the path property on authentication cookies.

Keith
  • 150,284
  • 78
  • 298
  • 434
3

The cookie is set at the domain level and is static. By default, the FormsAuthentication uses the TLD to set it, in this case {mySite.com}. In order to make it specific, you would have to tell it to use client1Name.{mySite.com}. In doing so, however, you would limit the cookie to that specific subdomain and the subdomain client2Name would no longer be able to access the cookie.

The path of the cookie restricts the subfolder that the cookie applies to. In the case of FormsAuthentication, again the default is set to the root /. You can manually set it to something else, but again, by setting it to /prospect1Name, all other folders immediately lose access to the cookie.

I'm not sure what behavior you are attempting to produce using these constraints, but it is unlikely that the cookie is the appropriate tool to do it. Mucking with the domain will limit the effectiveness of your authentication controls (unless that's precisely what you're trying to do).

Joel Etherton
  • 37,325
  • 10
  • 89
  • 104
  • 1
    Sorry, I thought I was quite clear - _client1Name.{mySite.com}_ cookies should never be sent to _client2Name.{mySite.com}_ and vice versa. I realise that you can manually set the cookie, but then you can't use `FormsAuthentication.SetAuthCookie` – Keith Jul 15 '11 at 15:25
  • @Keith: If you want to provide separation between domains, you simply need to set `EnableCrossAppRedirects=false` in the web.config attribute of the FormsAuthentication element. As for each folder, you can also specify the `Path` attribute in the same element, but this would need to be applied in each separate application/folder. I have never attempted to override a parent FormsAuthentication element from within a child folder before, but I'm sure it's possible. – Joel Etherton Jul 15 '11 at 15:32
  • Cheers for the help (+1) - it turns out that the reason that you can't do this is that you really shouldn't (I had to figure that out the hard way). I've detailed why in an alternate answer. – Keith Aug 04 '11 at 11:06
  • I'm late to the party, but this helped me fix a prob with PROD and TEST web apps on the same host: logging into TEST would log you out of PROD, and vice-versa because they stomped on each others' auth cookie. I tried `enableCrossAppRedirects="false"` in the `forms` configuration node, but it didn't work for me because (obviously by the name) it is for passing auth across apps. To fix, all I had to do was change each app's `forms` `name` attribute in `web.config` to a unique value instead of default value of `.ASPXAUTH`, and viola! DUH! Always assumed it had to be `.ASPXAUTH` (bad assumption). – nothingisnecessary Oct 01 '14 at 19:25