Improving Performance of SimpleMembership By Using Claims-Based Access Control

Claims-based identity and access control became first class citizens with the introduction of .NET version 4.5.  This along with integrating Windows Identify Foundation (WIF) has added some very powerful security features to .NET 4.5.  In this article I will look how we can make a simple changes to SimpleSecurity to make it more efficient and reduce the number of times we need to hit the database during authorization by retrieving some of the information we need from claims. If you are not familiar with claims there is a good introduction here.

I originally introduced the idea of  decoupling the security model from the application by creating a custom AuthorizeAttribute that accepted a resource and operation instead of a role. You can read more about it here and some later improvements here.  In these designs I override the OnAuthorization method and it looked like this.

   public override void OnAuthorization(AuthorizationContext actionContext)
   {
       base.Roles = ResourceService.GetRolesAsCsv(_resourceId, _operation);
       base.OnAuthorization(actionContext);
   }

This was implemented by getting a list of roles as a comma separated string that was added to the base Roles property and then called the base OnAuthorization method to complete the authorization process. Pretty straight forward. The problem I found is that calling the base OnAuthorization method requires two database calls; one to get the user ID from the user name of the current principal and then look up in the webpages_UsersInRoles table and the joined webpages_Roles table to see if there is a match with the list of roles provided to the OnAuthorization method.

It turns out that is completely unnecessary because the claims in the current principal already contain the roles for the user.  And this information is persisted in the authentication cookie so that it does not have to hit the database for every HTTP request.  So how do we take advantage of this.  First of all in our custom AuthorizeAttribute we override the AuthorizeCore method instead of the OnAuthorization method. It looks like this:

protected override bool AuthorizeCore(HttpContextBase httpContext)
{
    //Get the current claims principal
    var prinicpal = (ClaimsPrincipal)Thread.CurrentPrincipal;
    //Make sure they are authenticated
    if (!prinicpal.Identity.IsAuthenticated)
        return false;
    //Get the roles from the claims
    var roles = prinicpal.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray();
    //Check if they are authorized
    return ResourceService.Authorize(_resourceId, _operation, roles);
}

The steps are described in the comments. First we get the ClaimsPrincipal of the current user.  Then we check to make sure they are authenticated. If they are not then we just return false and the authorization will fail.  Next we query the claims using LINQ to get a string array of roles.  This is then passed to a method to see if we have can match any of the user's roles to roles assigned to the resource/operation.  If there is a match it returns true, if not it returns false.  We still have to do a database look-up to get the roles assigned to the resource/operation, but we have eliminated several queries to get the users roles.

Now you might be thinking, why don't we put the roles for all of the resources and operations in the claims also.  It turns out that .NET 4.5 makes it easy to put additional claim information into the current principal and even handles breaking the information into multiple cookies if it gets too large.  But that is a topic for a future article.  Adding this information to the claims would depend on the application and the number of resources, operations, and roles used.  We do not want to make the cookie too large and pass it around all of the time.

I will tackle adding more claims in another article because there is other useful information we can add to the claims.  For example, the user ID, which is the foreign key in a lot of other databases/tables for tying user information to other information in the system.  Usually when you want to query information based on the current user you get the user name from the principal, do a database query to get the user ID, and then query for the information we a looking for. We can eliminate this intermediate step by putting the user ID in the claims.

You can get the source code and example application from the SimpleSecurity Project on CodePlex. Just go to the Source Code tab and select the download link.


Comments

Popular posts from this blog

Using Claims in ASP.NET Identity

Customizing Claims for Authorization in ASP.NET Core 2.0

Seeding & Customizing ASP.NET MVC SimpleMembership