1

I've got a web site that implements its own Forms based login, and creates an authentication cookie like this:

    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userID, DateTime.UtcNow, expiration, isPersistent, userFunctions);
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
    cookie.Expires = expiration;
    HttpContext.Current.Response.Cookies.Add(cookie);

The variable "userFunctions" contains a comma-separated list of roles that the user is a member of.

In my Global.asax file I'm retrieving those user functions in the following way:

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.User != null)
    {
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
            if (HttpContext.Current.User.Identity is FormsIdentity)
            {
                FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;

                string[] roles = id.Ticket.UserData.Split(',');
                HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
            }
        }
    }
}

All this is working great. Or it was until I had to change it for a whole new bunch of users. The problem with the new users is that the "userFunctions" variable can get really long, and is way too long to store in a cookie (that is limited in size to something like 4k).

I would change my code to store the "userFunctions" in session, but session is not available to Application_AuthenticateRequest. I could possibly store the data in the application cache (maybe in a key/value pair) but I hesitate to do that as the application cache doesn't seem the 'right' place to put this data.

I probably will end up putting it in the application cache, but before I do I thought I'd ask and see if anybody has a better alternative?

paulH
  • 1,102
  • 16
  • 43
  • Why do you add the list of roles the user is a member of to the cookie? – Jeroen Dec 04 '12 at 16:41
  • 1
    surely if you send this list to a client (as a cookie) the end user can change it? I wouldn't have thought you'd want this. If you're going to maintain this list between calls I'd suggest it stays on the server. – PeteH Dec 04 '12 at 16:50
  • I agree. This definitely sounds like server side functionality. Maybe you could split up your authentication / authorisation functionality to accomplish this... – SpruceMoose Dec 04 '12 at 17:39
  • @Jeroen - I store the roles in the cookie because that is the code that I inherited. – paulH Dec 04 '12 at 18:31
  • @PeteH / Cal279: I agree. It should stay on the server and I want to rewrite this portion of code so that it does stay on the server, but because Session State is not available I can't find anywhere logical and suitable to store the roles so that I can add them back in to HttpContext.Current.User – paulH Dec 04 '12 at 18:35
  • The code you inherited? So you don't have a clue about what you're doing? The only purpose of that cookie is to know who is authorized. It shouldn't contain more than a userId. Everything else you keep server side and put it in a session or load it from your DB in the moment you need it. – Jeroen Dec 05 '12 at 00:20
  • @Jeroen presumably whoever wrote this thought that it'd be quicker to read a cookie than perform a database lookup for each hit. And let's face it they were probably correct. Shame they neglected security to do this though ;-) – PeteH Dec 05 '12 at 10:04
  • @PaulH is your whole site stateless (i.e. no use of Session anywhere)? If not, you are of course not restricted to Application_AuthenticateRequest being the place where you have to do this check. Is it practical to call at the top of every page for example? – PeteH Dec 05 '12 at 10:12
  • @PeteH - You're right, I think the original purpose was to avoid hitting the database on every page request. As for security, the authentication cookie is encrypted and I think I read that .net does something to check that the cookie hasn't been tampered with? I still want to change it though, and we do use Session in the site. I'm just not clear at exactly what point in the Lifecycle the Session gets enabled, and at exactly what point in the Lifecycle the application starts doing role-based security checks, and whether there's anywhere between those two points where I can update the roles. – paulH Dec 05 '12 at 10:23
  • Ok, so having gone away and done a bit of research, I've found that .net performs user authorization during the AuthorizeRequest event, but doesn't make Session state available under later in the lifecycle, during the AcquireRequestState event. I did a test to confirm this by updating HttpContext.Current.User with roles from Session during Application_AcquireRequestState. The code works fine and retrieves the roles from Session, but users cannot view any role restricted page, because Authorization has already happened before I update the User object. – paulH Dec 05 '12 at 11:27

1 Answers1

1

Given that I cannot use Session to store user roles (as I cannot retrieve them before Authorization has taken place), and I didn't want the expense of making a trip to the database on every page request, I ended up storing the roles in the Application Cache:

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.User != null)
    {
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
            if (HttpContext.Current.User.Identity is FormsIdentity)
            {
                FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;

                string[] roles;
                string cachedRoles = (string)HttpContext.Current.Cache.Get("UserFunctions" + id.Name.ToLower());
                if (cachedRoles == null)
                {
                    // Reload UserFunctions and add back in to Cache.

                    cachedRoles = [...code to get UserFunctions from database...];

                    HttpContext.Current.Cache.Insert("UserFunctions" + id.Name.ToLower(), cachedRoles, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(0, 20, 0), System.Web.Caching.CacheItemPriority.NotRemovable, null);

                }

                roles = cachedRoles.Split(',');

                HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
            }
        }
    }
}

It seems to work ok (though with limited testing so far).

paulH
  • 1,102
  • 16
  • 43
  • 2
    Using Cache in this way is a perfectly reasonable solution. However I'd do this by implementing a custom RoleProvider, rather than putting the code in global.asax. See the following article for details of how to do this: http://msdn.microsoft.com/en-us/library/8fw7xh74(v=vs.100).aspx – Joe Dec 05 '12 at 13:45