Decoupling Your Security Model Revisited

I while back I wrote an article "Decoupling You Security Model From The Application Model With SimpleMembership".  In this article I proposed a design change to how the AuthorizeAttribute is used that provides a decoupled security model that is more flexible as your ASP.NET MVC application evolves.  One reader provided good comments on what he did not like about the design, with one major issues being the use of magic strings to define the resources and operations that we are authorizing against.  If you recall from the previous article we define the custom AuthorizeAttribute like this.

    [SimpleAuthorize(Resource = "UserProfile", Operation = "modify")]
    public ActionResult ModifyUserProfile()
    {
        ViewBag.Message = "Modify Your Profile";
        return View();
    }

This is a valid concern so I revisited this design and came up with a solution that eliminates magic strings and will actually perform better. Here is what our SimpleAuthorize looks like now.

    [SimpleAuthorize(AppResources.UserProfile, Operations.Update)]
    public ActionResult ModifyUserProfile()
    {
        ViewBag.Message = "Modify Your Profile";
        return View();
    }

In the old design there was an Operations table that contained all of the operations for each resource. This normalized design was very flexible and allowed for unlimited operations per resource. But in reality there are usually just a few operations used and they are mostly for your CRUD operations. In the new design I eliminated the Operations table and added them directly to the Resource table as an enumerated type.  Here is how I defined my Operations.

    [Flags]
    public enum Operations : int
    {
        None = 0,
        Create = 1,
        Read = 2,
        Update = 4,
        Delete = 8,
        Execute = 16,
        All = 31
    }

Notice that I have defined this enum as Flags. This allows me to store any combination of operations in this enumerated type using bit operations. I can also use the Enum.HasFlag method to see if an individual operation is in an instance of the enum.  I added the types None and All for ease of initialization; they are not considered valid operations.

I also changed the identifier for the Resource table to not be generated by the database.  This way the developer can use an enumerated type or static object with constants to define the resources available in their application. Here is how the I initialized the resources and operations in the Seed method of the database initializer used in the SeedSimple example application.

    //Build the operations available for this resource
    Operations operations = Operations.Read;
    operations |= Operations.Create;
    //Add the resource
    ResourceService.AddResource(AppResources.Foo, "Foo", operations);
    //Map the resource/operation to the roles
    ResourceService.MapOperationToRole(AppResources.Foo, Operations.Read, "User");
    ResourceService.MapOperationToRole(AppResources.Foo, Operations.Create, "Admin");

Unfortunately we still have to use magic strings for the roles because Microsoft designed it that way.  Besides eliminating the magic strings for SimpleAuthorize filter this design will perform better because we do not have to join the Resource and Operations tables and we are performing look-ups on integers instead of strings.

As always you can get this code from the SimpleSecurity Project on CodePlex to look into more of the details and play around with the code.  Let me know what your thoughts are in the comments on whether this is a better design or not.


Comments

Popular posts from this blog

Using Claims in ASP.NET Identity

Seeding & Customizing ASP.NET MVC SimpleMembership

Customizing Claims for Authorization in ASP.NET Core 2.0