Creating Custom Authorization Policy Provider In ASP.NET Core

Introduction
 
In my previous article, I talked about policy-based authorization. We can register all the required policies using the AddPolicy method of AuthorizationOptions class. If we have a large number of policies, this is not a desirable way to register all policies. In such a case, we can use a custom policy provider (IAuthorizationPolicyProvider).
 
The following scenarios may be a candidate for Custom Authorization Policy Provider:
  • Policy evaluation provided by the external service
  • For a large range of policies (i.e., a variety of policies are to provide access. It doesn't make any sense to add every policy to authorization options)
  • Create a Policy that uses runtime information to provide access
Example
 
In my previous article, I have explained how to add the example of "minimum time spent" policy. Here, I have added a policy that allows access for the employees who have completed 365 days in the company. If the different controller action is available for different days completed in the company employee, it does not make any sense to add many policies to AuthorizationOptions.AddPolicy method. In such a case, we can use IAuthorizationPolicyProvider (custom policy provider).
 
The IAuthorizationPolicyProvider interface is used to retrieve authorization policies. The DefaultAuthorizationPolicyProvider is registered and used to retrieve authorization policies that are provided by the AuthorizationOptions in and authorization method. We can customize the behavior by doing the different implementation of IAuthorizationPolicyProvider interface. This interface contains following two API
  • GetPolicyAsync
    This method returns authorization police for a provided name

  • GetDefaultPolicyAsync
    This method returns the default authorization policy that used by "Authorize" attribute without specifying any policy.
I have different action methods in controller and these actions can be accessed by different group of user i.e. "Method1" can be accessed by user who completed 365 or more days in company, "Method2" is accessed by the user who completed 180 or more days in company and "Method3" is accessed by the user who completed 10 or more days in company. This requirement can be achieved by creating a custom authorization policy provider using IAuthorizationPolicyProvider.
 
Here, I will create a policy runtime and assign this policy to customize authorize attribute. The authorization policy is identified by the name and policy name will be generated based on the custom authorize attribute parameter.
 
I can achieve my requirement using the following four steps
 
Step 1 - Create a custom Authorize filter
 
The first step to creating a custom authorize attribute that accepts the number of days as input based on the input value is to generate a policy name and assign "Policy" property of the base class. So, when executing this filter, it will consider policy rules that are provided to validate the user's access.
 
In the following example code, I have created "MinimumTimeSpendAuthorize" attribute and based on input it dynamically created policy name i.e. "MinimumTimeSpend.{input value}" and assigned it to Policy property. This policy can be created and registered in the next step.
  1. using Microsoft.AspNetCore.Authorization;  
  2.   
  3. namespace PolicyBasedAuthorization.Policy  
  4. {  
  5.     public class MinimumTimeSpendAuthorize : AuthorizeAttribute  
  6.     {  
  7.         public MinimumTimeSpendAuthorize(int days)  
  8.         {  
  9.             NoOfDays = days;  
  10.         }  
  11.   
  12.         int days;  
  13.   
  14.         public int NoOfDays  
  15.         {  
  16.             get  
  17.             {  
  18.                 return days;  
  19.             }  
  20.             set  
  21.             {  
  22.                 days = value;  
  23.                 Policy = $"{"MinimumTimeSpend"}.{value.ToString()}";  
  24.             }  
  25.         }  
  26.     }  
  27. }  
Step 2 - Create Authorization Requirement and Authorization handler
 
The Authorization Requirement is the collection of data which can be used to evaluate the user principal and the Authorization handler contains an evaluation mechanism for properties of requirement. One requirement may be associated with multiple handlers.
 
Here, I have created the requirement for minimum time spent for the organization and created the handler that calculates no of days for an employee by subtracting today date from claim "DateOfJoing" and if the result is greater than or equal to supplied data then the user is authorized to access.
 
Authorization Requirement
  1. using Microsoft.AspNetCore.Authorization;  
  2.   
  3. namespace PolicyBasedAuthorization.Policy  
  4. {  
  5.     public class MinimumTimeSpendRequirement : IAuthorizationRequirement  
  6.     {  
  7.         public MinimumTimeSpendRequirement(int noOfDays)  
  8.         {  
  9.             TimeSpendInDays = noOfDays;  
  10.         }  
  11.   
  12.         public int TimeSpendInDays { get; private set; }  
  13.     }  
  14. }  
Authorization handler 
  1. using Microsoft.AspNetCore.Authorization;  
  2. using System;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace PolicyBasedAuthorization.Policy  
  6. {  
  7.     public class MinimumTimeSpendHandler : AuthorizationHandler<MinimumTimeSpendRequirement>  
  8.     {  
  9.         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumTimeSpendRequirement requirement)  
  10.         {  
  11.             if (!context.User.HasClaim(c => c.Type == "DateOfJoining"))  
  12.             {  
  13.                 return Task.FromResult(0);  
  14.             }  
  15.   
  16.             var dateOfJoining = Convert.ToDateTime(context.User.FindFirst(  
  17.                 c => c.Type == "DateOfJoining").Value);  
  18.   
  19.             double calculatedTimeSpend = (DateTime.Now.Date - dateOfJoining.Date).TotalDays;  
  20.   
  21.             if (calculatedTimeSpend >= requirement.TimeSpendInDays)  
  22.             {  
  23.                 context.Succeed(requirement);  
  24.             }  
  25.             return Task.FromResult(0);  
  26.         }  
  27.     }  
  28. }  
Step 3 - Create custom policy provider
 
In this step, we will create the policy by implementing IAuthorizationPolicyProvider. This interface has two abstract methods: GetDefaultPolicyAsync and GetPolicyAsync. In a GetDefaultPolicyAsync method, I will return all the policies that are provided by the default provider. In a GetPolicyAsync method, I will check if the policy name pattern is following a pattern (i.e. MinimumTimeSpend.{input value}) generated in custom authorize attribute. If the pattern matches, then I will extract the number of days from policy name and supplied requirement.
  1. using System;  
  2. using System.Threading.Tasks;  
  3. using ClaimBasedPolicyBasedAuthorization.Policy;  
  4. using Microsoft.AspNetCore.Authorization;  
  5. using Microsoft.Extensions.Options;  
  6.   
  7. namespace PolicyBasedAuthorization.Policy  
  8. {  
  9.     public class MinimumTimeSpendPolicy : IAuthorizationPolicyProvider  
  10.     {  
  11.         public DefaultAuthorizationPolicyProvider defaultPolicyProvider { get; }  
  12.         public MinimumTimeSpendPolicy(IOptions<AuthorizationOptions> options)  
  13.         {  
  14.             defaultPolicyProvider = new DefaultAuthorizationPolicyProvider(options);  
  15.         }  
  16.         public Task<AuthorizationPolicy> GetDefaultPolicyAsync()  
  17.         {  
  18.             return defaultPolicyProvider.GetDefaultPolicyAsync();  
  19.         }  
  20.   
  21.         public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)  
  22.         {  
  23.             string[] subStringPolicy = policyName.Split(new char[] { '.' });  
  24.             if (subStringPolicy.Length > 1 && subStringPolicy[0].Equals("MinimumTimeSpend", StringComparison.OrdinalIgnoreCase) && int.TryParse(subStringPolicy[1], out var days))  
  25.             {  
  26.                 var policy = new AuthorizationPolicyBuilder();  
  27.                 policy.AddRequirements(new MinimumTimeSpendRequirement(days));  
  28.                 return Task.FromResult(policy.Build());  
  29.             }  
  30.             return defaultPolicyProvider.GetPolicyAsync(policyName);  
  31.         }  
  32.     }  
  33. }  
Step 4 - Register Handler and Policy Provider
 
The next and final step is to register the authorization handler and policy provider. To use custom policies, we must register authorization handler that is associated with the requirement used in customs policy and also we need to register custom policy provider in ConfigureServices method of startup class.
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     ...  
  4.     ....  
  5.     services.AddTransient<IAuthorizationPolicyProvider, MinimumTimeSpendPolicy>();  
  6.     services.AddSingleton<IAuthorizationHandler, MinimumTimeSpendHandler>();  
  7.   
  8.     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);  
  9.     services.AddAuthorization();  
  10. }  
To get the desired result, we need to apply custom authorize filter to controller action method. In the following example code, "Method1" can be accessed by the user who completed 365 or more days in a company, "Method2" is accessed by the user who completed 180 or more days in a company and "Method3" is accessed by the user who completed 10 or more days in the company.
  1. public class DemoController: Controller  
  2. {  
  3.     [MinimumTimeSpendAuthorize(180)]  
  4.     public IActionResult TestMethod2()  
  5.     {  
  6.         return View("MyPage");  
  7.     }  
  8.     [MinimumTimeSpendAuthorize(365)]  
  9.     public IActionResult TestMethod1()  
  10.     {  
  11.         return View("MyPage");  
  12.     }  
  13.     [MinimumTimeSpendAuthorize(10)]  
  14.     public IActionResult TestMethod3()  
  15.     {  
  16.         return View("MyPage");  
  17.     }  
  18. }  
Summary
 
Using the method described in this article, we can create custom authorization policy providers using IAuthorizationPolicyProvider. It is very useful when we have a large range of policies or a policy created based on runtime information.
 
You can view or download the source code from the GitHub link here.