Forms authentication and role-based security (II)

In this second part about Forms authentication and role based security I will show you how you can overcome the “limitations” (more than 50 roles per user) showed on my first post about this topic. The strategy presented on that first post was related to the roles state persistence inside the FormsAuthentication cookie. If you don’t want to save the roles info inside a cookie because you might have users with cookies disabled usage or perhaps some of your users have a huge amount of roles or whatever reason you may have, you may store this info on the server side using for example the System.Web.Caching.Cache object. This cache has an excellent performance and scalability management and is thread safe as well.  However there are two drawbacks that we need to solve. The first one is related to the rather likely key collision issues that may arise because of its singleton nature (one instance per AppDomain). We’ll solve this issue assigning a unique key (i.e. a Guid value) to each user principal that we store (this time we‘ll store the created principal with the roles collection inside instead of just storing the roles array). The other issue is about scalability and how many users we can store without a high impact on memory resources. Fortunately the Cache object give us an invalidation strategy that expires each item with a somewhat flexible timeout configuration scheme so we ‘ll align the FormsAthentication ticket timeout with the cache item timeout in order to limit the user’s principal lifetime and therefore achieve a better scalability path.Now let’s see some code about all of this. First we need to store the unique key to fetch the user principal inside the FormsAuthentication cookie. We may do this at the login form so we don’t have to do this later. Here is the code that will do this (BTW is almost identical to the last post but now we store the Guid instead of the roles string).   
    // Get the cookie created by the FormsAuthentication API  // Notice that this cookie will have all the attributes according to     // the ones in the config file setting.         HttpCookie cookie = FormsAuthentication.GetAuthCookie( UserId.Text, false );  FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);   // Store the Guid inside the Forms Ticket with all the attributes aligned with   // the config Forms section.  string uniqueKey = Guid.NewGuid().ToString("N");   FormsAuthenticationTicket newticket = new FormsAuthenticationTicket(                                                              ticket.Version,                                                               ticket.Name,                                                                  ticket.IssueDate,                                                             ticket.Expiration,                                                            ticket.IsPersistent,                                                          uniqueKey,                                                      ticket.CookiePath);             // add the encrypted ticket to the cookie as data.                                           cookie.Value = FormsAuthentication.Encrypt(newticket);  // Update the outgoing cookies collection.  Context.Response.Cookies.Set(cookie);   // Redirect the user to the originally requested page  Response.Redirect( FormsAuthentication.GetRedirectUrl( newticket.Name,                                                     newticket.IsPersistent ) ); 
  On the “Application_AuthenticateRequest” we have the code that will retrieve the user principal using the unique key gathered from the FormsAuthentication cookie or if we don’t found it we ‘ll create a new principal with a fresh roles retrieval. This will be the prize to pay each time you don’t find the principal in the cache (this will be the scenario on the first hit on each server of a web farm).  
protected void Application_AuthenticateRequest(Object sender, EventArgs e){   if(Context.Request.IsAuthenticated)   {      // retrieve user's identity from httpcontext user      FormsIdentity identity = (FormsIdentity)Context.User.Identity;       // First, let see if we found in the Cache.      IPrincipal principal = (IPrincipal)m_cache[ identity.Ticket.UserData ];       if( principal == null )      {          // Cache is thread safe and we'll have usually only one thread          // asking for a specific user key.          Principal = PrincipalFactory( identity.Name );          // Add the principal to the Cache with the expiration item in synchro          // with the FormsAuthentication ticket timeout          m_cache.Insert( identity.Ticket.UserData, principal, null,                        Cache.NoAbsoluteExpiration, identity.Ticket.Expiration.Subtract(                   identity.Ticket.IssueDate ), CacheItemPriority.Default, null);      }       // create principal and attach to user      Context.User = principal;   }} 
 As you may already guess, “PrincipalFactory” creates a new instance of the Principal object with the user’s roles in it.All in all, we saw the other strategy (server side state storage) of using Forms authentication with custom principals in a performance efficient way.

                                            

  

5 Comments

Comments have been disabled for this content.