Performing Authorization In Class Libraries Without Coupling Security in ASP.NET Identity

Most of the time it makes sense to perform authorization at the Controller or Web API level in an ASP.NET MVC application using an AuthorizeAttribute on the controller or action.  This handles at least 95% of the scenarios but occasionally it makes sense to handle authorization down in a class library or it needs to be handled in code for more complex situations. An example might be an application that allows users to create folders and files that have individual permissions.  In this scenario the resource being acted upon must be determined at run-time and therefore we cannot use a static AuthorizeAttribute.

So how do we determine permissions on a dynamic resource without coupling security with our application domain. This is a good time to use a custom ClaimsAuthorizationManager.   A ClaimsAuthorizationManager will allow a class library to loosely couple authorization in our application domain and configure it at deployment.  Here is an example on using the ClaimsAuthorizationManager that I added to the SimpleSecurity project using ASP.NET Identity.  You can get the source code for this example here.

Here is how we would insert an authorization check in our class library.

    public class FakeService
    {
        public static void MyMethod()
        {
            //Here is where we would check access to perform operations in this method.
            ClaimsPrincipalPermission.CheckAccess(AppResources.Foo.ToString(), Operations.Read.ToString());
            //If we made it this far we were authorized and we would perform the actual operations here.
        }
    }


When calling the ClaimsAuthorizationManager you invoke the CheckAccess method which expects two parameters; a resource and an action. This provides context to the ClaimsAuthorizationManager on what to authorize. In this contrived example the resource and action are static, but as you can imagine they could just as easily have been derived from something like a database query or other application logic. Notice that the resource and action passed in fit nicely with the concepts I presented in an earlier article on decoupling the security model from your MVC application. If the authorization fails the ClaimsAuthorizationManager throws a SecurityException which we can handle in our controller or Web API.

What makes this method of authorization even more flexible is that we configure which ClaimsAuthorizationManager to use in the web.config. Here is what the configuration looks like for this example.

 <system.identityModel>
    <identityConfiguration>
      <claimsAuthorizationManager type="SimpleSecurity.AspNetIdentity.SimpleAuthorizationManager, SimpleSecurity.AspNetIdentity"/>
    </identityConfiguration>
  </system.identityModel>

This allows us to configure different ClaimsAuthorizationManagers for different web applications, further decoupling the authorization process from our application domain.

Here is what the code look like for this custom ClaimsAuthorizationManager.

    public class SimpleAuthorizationManager : ClaimsAuthorizationManager
    {
        public override bool CheckAccess(AuthorizationContext context)
        {
            string resourceStr = context.Resource.First().Value;
            string actionStr = context.Action.First().Value;
            int resourceId;
            Operations operationId;
            try { resourceId = Int32.Parse(resourceStr); }
            catch { throw new Exception("Invalid resource. Must be a string representation of an integer value."); }
            try { operationId = (Operations)Enum.Parse(typeof(Operations),actionStr); }
            catch { throw new Exception("Invalid action/operation. Must be a string representation of an integer value."); }

            //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, operationId, roles);
        }
    }

This authorization process gets the roles from the current authenticated users claims and checks in the database if any of those roles are associated with the resource and operation/action that is being performed. The resource and action passed in are strings so I need to convert them to the integer ID and enumerated operation used in the database.

If the authorization process fails a SecurityException is thrown. Here is how we can catch the exception and handle it in the controller.

        public ActionResult UseClaimsAuthorization()
        {
            try
            {
                FakeService.MyMethod();
            }
            catch (SecurityException ex)
            {
                Response.Redirect(loginUrl(Url.Action("UseClaimsAuthorization", "Home")));
            }
            ViewBag.Message = "Test Claims Authorization";
            return View();
        }

        public string loginUrl(string returnUrl)
        {
             return "../Account/Login?ReturnUrl=" + HttpUtility.UrlEncode(returnUrl);
        }



In this example I handle the SecurityException just like forms authentication would and redirect to the login page. In a Web API you could return an HTTP Unauthorized error.

You can try this out by downloading the source code from SimpleSecurity, compiling and running the application.  The example MVC application is in AspNetIdentity\SimpleSecurity.AspNetIdentity.RefApp. The database is seeded with a user who has a user name of "user" and password of "password" that has the role for the resource/operation authorized in this example.

I think this is a good method for handling security in your class libraries and lower layers of the application without tightly coupling your security model. What do you think? Can you think of some applications for this technique.

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