facebook

Blog

Stay updated

Let’s see how to secure an ASP.NET Core application using Policies and Claims
Security in ASP.NET Core with Policies and Claims
Wednesday, January 29, 2020

When it comes to security in an application, you always have to think of something indispensable but not always easy to implement. Over the years, I have used different authorization models: from the classic role-Based to custom authorizations, written ad hoc for the application domain. In this article, we analyze the new authorization model based on the policies introduced by .NET Core, which can easily adapt to a wide variety of scenarios.

To fully understand this model, we need a quick overview of role-based permissions, probably the most widely used model, from whose limitations emerge the potential of the policy-based model.

role is nothing more than a string that identifies a permission set for an authenticated user in the system. The role-based authorization model allows, in .NET, the use of the Authorize attribute to restrict access to a resource, based on the specified role. The latter is applied to a controller or to an action. The following code shows how to restrict access to the “ReportController” to system administrators only, that is users who are members of the “Administrator” role:

[Authorize(Roles = "Administrator")]
public class ReportController : Controller
{   
  //Code
}

Of course, you can specify multiple roles allowed to use a specific controller: just enter roles separated by a comma.

[Authorize(Roles = "Manager,Administrator")]
public class ReportController : Controller
{
   //Code
}

The attribute Authorize, as mentioned above, is applicable both at the level of controller and action, this allows us to limit access to specific features:

[Authorize(Roles = "Manager, Administrator")]
public class ReportController : Controller
{
    public ActionResult ViewReport()
    {
        //Code
    }
    [Authorize(Roles = "Administrator")]
    public ActionResult DeleteAllReports()
    {
        //Code
    }
}

We can restrict access to ReportController to users who belong to both roles “Manager” and “Administrator,” making the authorization more stringent.

[Authorize(Roles = "Manager")]
[Authorize(Roles = "Administrator")]
public class ReportController  : Controller
{
}

These examples highlight both the ease of use and the limitations of this model. Imagine, for example, the scenario where your domain does not have a single figure of “Administrator,” but multiple versions of it, a “CustomerAdministrator,” a “ProductAdministrator,” a “SuperAdministrator”. Your application will have to take all these figures into account, increasing the granularity of your permissions: when the number of roles increases, the management difficulty increases with it. It is precisely in these scenarios that the policy-based model can make a difference.

Let’s start with the three main concepts: Policy, Requirements, and Handlers. A policy is a set of requirements; a requirement is a set of parameters that are used to validate the identity of the user, while a handler is used to determine whether a user has access to a specific resource using the parameters contained in the requirements.

A policy is usually registered at the startup of the application, more precisely in the ConfigureServices() method of the class Startup.cs.

services.AddAuthorization(options =>
  {
    options.AddPolicy("RequireManagerOnly", policy =>
      policy.RequireRole("Manager","Administrator"));
  });

Applying a registered policy is an effortless operation: use the attribute authorize already seen above, in a slightly different form:

[Authorize(Policy = "ShouldBeEmployeeOnly")]
public class ReportController : Controller
{
    [Authorize(Policy = "RequireAdminOnly")]
    public ActionResult DeleteReports()
    {
        //code
    }
}

The first thing that strikes you is the greater expressivity of the policy: another developer, who will work on your code, will certainly be facilitated in understanding how you have protected a specific functionality.

The roles of our domain have been used in the example. But this is not the only way available: we can also use Claims to express policy requirements.

A Claim is nothing more than a key/value pair that identifies a feature of a subject, such as name, age, document number, and more. In this way, we can express the requirements of a policy through the control of the value contained in a determined Claim, as, for example, to enable a specific function only to the adult customers.

The registration of policy requirements through claims is done by defining the policy itself. If we want to create a policy that allows access based on the existence of a specific claim for an authenticated user in the system, we can do it this way:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("ShouldBeOnlyEmployee", policy =>
              policy.RequireClaim("EmployeeId"));
    });
}

Once registered, the policy is usable through the Authorize attribute on a controller or on a specific Action.

[Authorize(Policy = "ShouldBeOnlyEmployee")]
public IActionResult SomeMethod()
{
    //Write your code here
}

As mentioned above, you can express policy requirements by checking the value contained in a particular claim. Here is the code snippet that performs this check on the value contained in the claim “IsAdmin”:

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddMvc();
   
    services.AddAuthorization(options =>  
    {  
        options.AddPolicy("CustomSecurityPolicy", policy =>
            policy.RequireClaim("IsAdmin", "true"));  
    });  
}

In the examples, handlers were implicit. Let’s see now how we can create a custom requirement to use with our policies, and possible handlers to manage it.

One requirement, in .NET Core, is a class that implements the interface IAuthorizationRequirement and acts as a container of the parameters with which the requirement will be managed. Here is an example:

public class MinimumYearsInCompanyRequirement : IAuthorizationRequirement
{
    public int MinimumYears { get; set; }
    public MinimumYearsInCompanyRequirement(int experience)
    {
        MinimumYears = experience;
    }    
}

A requirement may have one or more handlers, which is used to evaluate its properties. A handler is nothing more than a class that extends AuthorizationHandlerT> and implements the HandleRequirementAsync() method.

public class MinimumYearsHandler :
  AuthorizationHandler<MinimumYearsInCompanyRequirement>
    {
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            MinimumYearsInCompanyRequirement requirement)
        {
            throw new NotImplementedException();
        }
    }

Below, the code that contains a simple implementation of the handler, which finds the claim and evaluates the Requirement:

public class MinimumYearsHandler : AuthorizationHandler<MinimumYearsInCompanyRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinimumYearsInCompanyRequirement requirement)
    {
        var user = context.User;
        var claim = context.User.FindFirst("MinYears");
        if(claim != null)
        {
            var expInYears = int.Parse(claim?.Value);
            if (expInYears >= requirement.MinimumYears)
                context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

As you can see, in case the evaluation of the parameters of the requirement has succeeded, you only need to call the method Succeed() of the AuthorizationHandlerContext and pass to it the instance of the Requirement like argument, and thus making it satisfied and enable the functionality that you have protected.

The registration of Handlers and custom Requirements is always done at the startup of the application in the ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc(;
  services.AddAuthorization(options =>
    {
      options.AddPolicy("MinYears", policy =>
          policy.Requirements.Add(
              new MinimumYearsInCompanyRequirement(5)));
    });
  services.AddSingleton<IAuthorizationHandler,
      MinimumYearsHandler>();
}

Simple, expressive and powerful. Give a chance to it and you won’t regret.

See you at the next article!