Custom Authorization Policy Provider with Custom Authorize Attribute in Asp .Net Core 2.2 and above
The authorize attribute is used to authorize or control user access to application / controller / actions in Asp. Net Core. The built in [Authorize] attribute might not be suitable for all business cases where we must come up with our own implementation. This article explains implementing authorization by creating your own custom attribute or by creating Custom Authorization Policy Provider.
The custom authorize attribute can be implemented by two methods
Approach 1: Extending AuthorizeAttribute along with IAuthorizationFilter will be simplest way to implement custom authorization attribute in Asp. Net Core. Refer : https://www.craftedforeveryone.com/adding-your-own-custom-authorize-attribute-to-asp-net-core-2-2-and-above/
Approach 2: Creating Custom Authorization Policy Provider with Authorization Handler, Authorization Requirement and an Authorize Attribute is explain below
Approach 2: Creating Custom Authorization Policy Provider with Authorization Handler, Authorization Requirement and an Authorize Attribute.
This approach explains creating Custom Authorize Attribute in Asp. Net Core 2.2 and above by implementing a custom authorization policy provider and following Asp. Net core policy-based authorization pipeline.
The explanation of this approach is based on the source code at https://github.com/kaarthikin/asp_net_core_custom_authorize_attribute kindly clone or refer to the code for clear understanding.
Steps to be followed and classed to be created to execute this approach are as below
Step 1: Class extending AuthorizeAttribute
This attribute class will used on top of controller or action like Asp. Net core’s inbuild [Authorize] attribute.
GitHub Link :
The Policy Property of AuthorizeAttribute should be assigned with a value for the Custom Authorization Policy provider’s GetPolicyAsync function to be invoked. This is explained in detail at step 4.
// www.craftedforeveryone.com // Licensed under the MIT License. See LICENSE file in the project root for full license information. using Microsoft.AspNetCore.Authorization; namespace CustomAuthorizeAttribute.Approach2 { //Defines the Attribute which will be used in controller public class A2AuthorizePermission:AuthorizeAttribute { private string _permissions; public string Permissions { get { return _permissions; } set { _permissions = value; //The Policy property should be set for the GetPolicyAsync() Method in CustomAuthPolicyProvider to be invoked //Appending value "CustomAuthPermissionPolicy" is to identify which handler to be invoked from Custom Auth Policy Provider. //value "CustomAuthPermissionPolicy" is user defined, it can be any value and any delimiter based on user requirement. Policy = "CustomAuthPermissionPolicy:"+ _permissions; } } } }
Step 2: Class implementing IAuthorizationRequirement
This class serves multiple purpose
- Serves as TRequirement object for the class which extends AuthorizationHandler< TRequirement >. The word TRequirement represents the properties or variables that are required for the class extending AuthorizationHandler to execute its logic.
- Serves as a middle man and passes the input values from your custom Authorize Attribute class to class extending AuthorizationHandler class.
- On the GetPolicyAsync() method of your CustomAutorizationPolicyProvider, the policy is dynamically constructed against the class which implements IAuthorizationRequirement
In the below example, we have defined a property Permissions. The object for the class A2AuthorizePermissionRequirement will be created in the class CustomAuthPolicyProvider, with the Policy property value which was set in the Custom Authorize Attribute and passed as constructor input for A2AuthorizePermissionRequirement.
GitHub Link:
// www.craftedforeveryone.com // Licensed under the MIT License. See LICENSE file in the project root for full license information. using Microsoft.AspNetCore.Authorization; namespace CustomAuthorizeAttribute.Approach2 { //IAuthorizationRequirement is needed for creating AuthorizationHandler //and also when dynalically creating policy, object are created only for requirement which inturn invokes the AuthorizationHandler public class A2AuthorizePermissionRequirement: IAuthorizationRequirement { public string Permissions { get; private set; } public A2AuthorizePermissionRequirement(string permissions) { Permissions = permissions; } } }
Step 3: Class extending AuthorizationHandler
The TRequirement type of AuthorizationHandler will be the class which implements IAuthorizationRequirement(The class which was created in Step 2)
Override the function HandleRequirementAsync. The actual logic or our custom authorization logic is coded inside this function.
Set context.Succeed() to mark user as authorized and context.Fail() to set user as unauthorized and return Task.CompletedTask
GitHub Link:
If you have more than 1 authorize attribute and if user should be also authorized in lower levels or second attribute, then do not set context.Succeed() in first attribute itself. Setting so will authorize the user for the entire lifetime of the request and the control will not even be taken to the second or next level of authorize attributes.
// www.craftedforeveryone.com // Licensed under the MIT License. See LICENSE file in the project root for full license information. using Microsoft.AspNetCore.Authorization; using System.Linq; using System.Threading.Tasks; namespace CustomAuthorizeAttribute.Approach2 { //The actual logic of Authorize Attribute is defined in Handler. public class A2AuthorizePermissionHandler : AuthorizationHandler<A2AuthorizePermissionRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, A2AuthorizePermissionRequirement requirement) { if (string.IsNullOrEmpty(requirement.Permissions)) { context.Fail(); } var userName = context.User.Identity.Name; var assignedPermissionsForUser = MockData.UserPermissions.Where(x => x.Key == userName).Select(x => x.Value).ToList(); var requiredPermissions = requirement.Permissions.Split(","); foreach (var x in requiredPermissions) { if (assignedPermissionsForUser.Contains(x)) { //context.Succeed authorize's the user. //Note: If multiple authorize attributes are available, if you want the user to be authorized in both the all the attributes, then dont set the context.success here. Just return task completed //setting context.succeed here will not take the control next attribute, it will be marked as authorized in all lower level attributes. context.Succeed(requirement); return Task.CompletedTask; } } //Setting user as not authorized context.Fail(); return Task.CompletedTask; } } }
Step 4: Class implementing IAuthorizationPolicyProvider (Custom Authorization Policy Provider)
This class becomes your custom authorization policy provider class. Implement the GetDefaultPolicyAsync and GetPolicyAsync from the interface IAuthorizationPolicyProvider.
Register the class implementing IAuthorizationPolicyProvider in the startup.cs. When ever an inbuilt Authorize Attribute or some custom Authorize Attribute is executed, the Asp.Net Core pipeline invokes either GetDefaultPolicyAsync or GetPolicyAsync methods of your class implementing IAuthorizationPolicyProvider.
If no value is the Policy Property of Authorize Attribute or in your class which extends Authorize Attribute the GetDefaultPolicyAsync method of your class implementing IAuthorizationPolicyProvider will be invoked.
The GetPolicyAsync method is invoked when the Policy Property of the Authorize Attribute is assigned with the value.
All the Authorize Attributes or class extending Authorize Attribute, and which sets the Policy property will invoke the GetPolicyAsync methods of the class implementing IAuthorizationPolicyProvider.
To uniquely identity the attribute and to execute business logics based on different criteria’s or for different attribute prefix the Policy Property with a unique identifier of your choice. In the example code we have prefixed as “CustomAuthPermissionPolicy:”. Use a delimiter the split the prefix and the values, passed from authorize attribute. In the example we are using “:” as delimiter.
The actual Authorization logic resides in the class extending AuthorizationHandler. For your custom authorize policy provider to invoke the AuthorizationHandler the policy has to be dynamically constructed in the GetPolicyAsync method using the class AuthorizationPolicyBuilder.
Initialize the AuthorizationPolicyBuilder and using the AddRequirements method, specify the handler which must be invoked. The handlers which was created in Step 3, are of type IAuthorizationRequirement which was created in Step 2.
Initialize the object for the class which implements IAuthorizationRequirement that was created in Step 2 and add as requirement for the AuthorizationPolicyBuilder. This will in turn invokes your handler and executes the authorization logic.
GitHub Link:
// www.craftedforeveryone.com // Licensed under the MIT License. See LICENSE file in the project root for full license information. using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; using System; using System.Linq; using System.Threading.Tasks; namespace CustomAuthorizeAttribute.Approach2 { public class CustomAuthPolicyProvider : IAuthorizationPolicyProvider { public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } public CustomAuthPolicyProvider(IOptions<AuthorizationOptions> options) { //Initializing default policy provider. FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); } public Task<AuthorizationPolicy> GetDefaultPolicyAsync() { //If A1Authorize attribute is used in controller, and if this CustomAuthPolicyProvider is registered in startup.cs //When A1Authorize attribute is invoked, this function will be called. return FallbackPolicyProvider.GetDefaultPolicyAsync(); } //This method will be called by the asp.net core pipeline only when Authorize Attribute has Policy Property set public Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { try { //All custom policies created by us will have " : " as delimiter to identify policy name and values //Any delimiter or character can be choosen, and it is upto user choice var policy = policyName.Split(":").FirstOrDefault(); //Name for policy and values are set in A2AuthorizePermission Attribute var attributeValue= policyName.Split(":").LastOrDefault(); if (policy!=null) { //Dynamically building the AuthorizationPolicy and adding the respective requirement based on the policy names which we define in Authroize Attribute. var policyBuilder = new AuthorizationPolicyBuilder(); if (policy == "CustomAuthPermissionPolicy") { //Authorize Hanlders are created based on Authroize Requirement type. //Adding the object of A2AuthorizePermissionRequirement will invoke the A2AuthorizePermissionHandler policyBuilder.AddRequirements(new A2AuthorizePermissionRequirement(attributeValue)); return Task.FromResult(policyBuilder.Build()); } } return FallbackPolicyProvider.GetPolicyAsync(policyName); } catch(Exception) { return FallbackPolicyProvider.GetPolicyAsync(policyName); } } } }