Policy-Based And Role-Based Authorization In ASP.NET Core 3.0 Using Custom Handler

Introduction

 
Authentication is the process of determining or giving individual access to system or user based on their identity. This article demonstrates how to add custom Policy-based & Role-based Authorization in ASP.NET Core 3.0
 
In the simple case of Authentication, we generally use the username and the password for login and based on that we are providing access to the application, but in this case, the user can access all the resources of the application. By using Policy-based & Role-based Authorization process, we can provide access to particular area of application to the user based on the Role/Policy of the user.
 
In this demonstration, I am going to use cookies to store user information. Readers can use a database to store user information.
 
Prerequisites
  • Install .NET Core 3.0.0 or above SDK from here.
  • Install the latest version of Visual Studio 2019 Community Edition from here.

Steps for creating a web application

 
Please check my existing article,  Cookie Authentication In .NET Core 3.0 for project setup. Here I have explained step by step how to create project? and how to manage authentication?
 
Code for the Basic Authorization
 
If you want to add simple Authentication and want to define only Role/Policy with authorization tag for authorizing action/controller like in the below syntax, then you can go with simple configuration. AuthorizationPolicyBuilderclass helps you to achieve this requirement.
 
Add Authorize tag on controller with Policy/Role
  1. [Authorize(Policy = "UserPolicy")]  
  2. [Authorize(Roles = "Admin")]  
  3. public class HomeController : Controller  
  4. {  
  5.   
  6.   
  7. }  
 Add Authorize tag on action with Policy/Role
  1. [Authorize(Policy = "UserPolicy")]  
  2. public ActionResult UsersPolicy()  
  3. {  
  4.     var uses = new Users();  
  5.     return View("Users", uses.GetUsers());  
  6. }  
  7.   
  8. [Authorize(Roles = "User")]  
  9. public ActionResult UsersRole()  
  10. {  
  11.     var uses = new Users();  
  12.     return View("Users", uses.GetUsers());  
  13. }  
Update Startup.cs file with below code
  1. using System.Security.Claims;  
  2. using Microsoft.AspNetCore.Authorization;  
  3. using Microsoft.AspNetCore.Builder;  
  4. using Microsoft.AspNetCore.Hosting;  
  5. using Microsoft.Extensions.Configuration;  
  6. using Microsoft.Extensions.DependencyInjection;  
  7. using Microsoft.Extensions.Hosting;  
  8.   
  9. namespace CookieAuthenticationDemo  
  10. {  
  11.     public class Startup  
  12.     {  
  13.         public Startup(IConfiguration configuration)  
  14.         {  
  15.             Configuration = configuration;  
  16.         }  
  17.   
  18.         public IConfiguration Configuration { get; }  
  19.   
  20.         public void ConfigureServices(IServiceCollection services)  
  21.         {  
  22.             services.AddAuthentication("CookieAuthentication")  
  23.                  .AddCookie("CookieAuthentication", config =>  
  24.                  {  
  25.                      config.Cookie.Name = "UserLoginCookie"// Name of cookie   
  26.                      config.LoginPath = "/Login/UserLogin"// Path for the redirect to user login page  
  27.                  });  
  28.   
  29.             services.AddAuthorization(config =>  
  30.             {  
  31.                 var userAuthPolicyBuilder = new AuthorizationPolicyBuilder();  
  32.                 config.DefaultPolicy = userAuthPolicyBuilder  
  33.                                     .RequireAuthenticatedUser()  
  34.                                     .RequireClaim(ClaimTypes.DateOfBirth)  
  35.                                     .Build();  
  36.             });  
  37.             services.AddControllersWithViews();  
  38.         }  
  39.   
  40.         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
  41.         {  
  42.             if (env.IsDevelopment())  
  43.             {  
  44.                 app.UseDeveloperExceptionPage();  
  45.             }  
  46.             else  
  47.             {  
  48.                 app.UseExceptionHandler("/Home/Error");  
  49.                 app.UseHsts();  
  50.             }  
  51.             app.UseHttpsRedirection();  
  52.             app.UseStaticFiles();  
  53.   
  54.             app.UseRouting();  
  55.              
  56.             app.UseAuthentication();  
  57.   
  58.             app.UseAuthorization();  
  59.   
  60.             app.UseEndpoints(endpoints =>  
  61.             {  
  62.                 endpoints.MapControllerRoute(  
  63.                     name: "default",  
  64.                     pattern: "{controller=Home}/{action=Index}/{id?}");  
  65.             });  
  66.         }  
  67.     }  
  68. }  

Integrate Custom Authorization Handler

 
Step 1
 
Create a new folder with the name CustomHandler and add the below classes into it.
 
Class 1 - AuthorizationPolicyBuilderExtension: User defined static class for addding a new policy for Authorization using AuthorizationPolicyBuilder.
 
Class 2 - CustomUserRequireClaim: User defined class inherits from IAuthorizationRequirement interface. IAuthorizationRequirement interface does not contain any method. This is an empty marker service.
 
Class 3 - PoliciesAuthorizationHandler: User defined class inherits from AuthorizationHandler class. TheHandleRequirementAsync method of AuthorizationHandler class 
helps to validate user based on policy.
 
Class 4 - RolesAuthorizationHandler: Same like PoliciesAuthorizationHandler class. This class helps to validate user based on Role.
 
Code for CustomUserRequireClaim: Put the below lines of code intoCustomUserRequireClaim class. 
  1. using Microsoft.AspNetCore.Authorization;  
  2. using System.Linq;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace CookieAuthenticationDemo.CustomHandler  
  6. {  
  7.     public class CustomUserRequireClaim : IAuthorizationRequirement  
  8.     {  
  9.         public string ClaimType { get; }  
  10.         public CustomUserRequireClaim(string claimType)  
  11.         {  
  12.             ClaimType = claimType;  
  13.         }  
  14.     }  
  15. }  
Code for AuthorizationPolicyBuilderExtension - Put the below lines of code intoAuthorizationPolicyBuilderExtension class.
  1. using Microsoft.AspNetCore.Authorization;  
  2.   
  3. namespace CookieAuthenticationDemo.CustomHandler  
  4. {  
  5.     public static class AuthorizationPolicyBuilderExtension  
  6.     {  
  7.         public static AuthorizationPolicyBuilder UserRequireCustomClaim(this AuthorizationPolicyBuilder builder, string claimType)  
  8.         {  
  9.             builder.AddRequirements(new CustomUserRequireClaim(claimType));  
  10.             return builder;  
  11.         }  
  12.     }  
  13. }  
Code for PoliciesAuthorizationHandler - Put the below lines of code intoPoliciesAuthorizationHandler class.
  1. using Microsoft.AspNetCore.Authorization;  
  2. using System.Linq;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace CookieAuthenticationDemo.CustomHandler  
  6. {  
  7.     public class PoliciesAuthorizationHandler : AuthorizationHandler<CustomUserRequireClaim>  
  8.     {  
  9.         protected override Task HandleRequirementAsync(  
  10.             AuthorizationHandlerContext context,  
  11.             CustomUserRequireClaim requirement)  
  12.         {  
  13.             if (context.User == null || !context.User.Identity.IsAuthenticated)  
  14.             {  
  15.                 context.Fail();  
  16.                 return Task.CompletedTask;  
  17.             }  
  18.   
  19.             var hasClaim = context.User.Claims.Any(x => x.Type == requirement.ClaimType);  
  20.   
  21.             if (hasClaim)  
  22.             {  
  23.                 context.Succeed(requirement);  
  24.                 return Task.CompletedTask;  
  25.             }  
  26.   
  27.             context.Fail();  
  28.             return Task.CompletedTask;  
  29.         }  
  30.     }  
  31. }  
Code for RolesAuthorizationHandler - Put the below lines of code intoRolesAuthorizationHandler class.
  1. using CookieAuthenticationDemo.Models;  
  2. using Microsoft.AspNetCore.Authorization;  
  3. using Microsoft.AspNetCore.Authorization.Infrastructure;  
  4. using System.Linq;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace CookieAuthenticationDemo.CustomHandler  
  8. {  
  9.     public class RolesAuthorizationHandler : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationHandler  
  10.     {  
  11.         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,  
  12.                                                        RolesAuthorizationRequirement requirement)  
  13.         {  
  14.             if (context.User == null || !context.User.Identity.IsAuthenticated)  
  15.             {  
  16.                 context.Fail();  
  17.                 return Task.CompletedTask;  
  18.             }  
  19.   
  20.             var validRole = false;  
  21.             if (requirement.AllowedRoles == null ||  
  22.                 requirement.AllowedRoles.Any() == false)  
  23.             {  
  24.                 validRole = true;  
  25.             }  
  26.             else  
  27.             {  
  28.                 var claims = context.User.Claims;  
  29.                 var userName = claims.FirstOrDefault(c => c.Type == "UserName").Value;  
  30.                 var roles = requirement.AllowedRoles;  
  31.   
  32.                 validRole = new Users().GetUsers().Where(p => roles.Contains(p.Role) && p.UserName == userName).Any();  
  33.             }  
  34.   
  35.             if (validRole)  
  36.             {  
  37.                 context.Succeed(requirement);  
  38.             }  
  39.             else  
  40.             {  
  41.                 context.Fail();  
  42.             }  
  43.             return Task.CompletedTask;  
  44.         }  
  45.     }  
  46. }  
Changes In Users class - Update Users class as I mentioned. I have added Role and DateOfBirth extra properties here.
  1. using System.Collections.Generic;  
  2.   
  3. namespace CookieAuthenticationDemo.Models  
  4. {  
  5.     public class Users  
  6.     {  
  7.         public int Id { getset; }  
  8.         public string UserName { getset; }  
  9.         public string Name { getset; }  
  10.         public string EmailId { getset; }  
  11.         public string Password { getset; }  
  12.         public string Role { getset; }  
  13.         public string DateOfBirth { getset; }  
  14.   
  15.         public IEnumerable<Users> GetUsers()  
  16.         {  
  17.             return new List<Users>() { new Users { Id = 101, UserName = "anet", Name = "Anet", EmailId = "anet@test.com", Password = "anet123", Role="Admin" , DateOfBirth  = "01/01/2012"} };  
  18.         }  
  19.     }  
  20. }  
Changes In LoginController class - Update LoginController class as  I mention. I have added UserName and DateOfBirth extra claims here. Also, I have added UserAccessDenied action method. If the user has no policy/role then user will redirect to AccessDenied Page. 
  1. using CookieAuthenticationDemo.Models;  
  2. using Microsoft.AspNetCore.Authentication;  
  3. using Microsoft.AspNetCore.Mvc;  
  4. using System.Collections.Generic;  
  5. using System.Linq;  
  6. using System.Security.Claims;  
  7.   
  8. namespace CookieAuthenticationDemo.Controllers  
  9. {  
  10.     public class LoginController : Controller  
  11.     {  
  12.         [HttpGet]  
  13.         public ActionResult UserLogin()  
  14.         {  
  15.             return View();  
  16.         }  
  17.   
  18.         [HttpPost]  
  19.         public ActionResult UserLogin([Bind] Users userModel)  
  20.         {  
  21.             // username = anet  
  22.             var user = new Users().GetUsers().Where(u => u.UserName == userModel.UserName).SingleOrDefault();  
  23.   
  24.             if (user != null)  
  25.             {  
  26.                 var userClaims = new List<Claim>()  
  27.                 {  
  28.                     new Claim("UserName", user.UserName),  
  29.                     new Claim(ClaimTypes.Name, user.Name),  
  30.                     new Claim(ClaimTypes.Email, user.EmailId),  
  31.                     new Claim(ClaimTypes.DateOfBirth, user.DateOfBirth),  
  32.                     new Claim(ClaimTypes.Role, user.Role)  
  33.                  };  
  34.   
  35.                 var userIdentity = new ClaimsIdentity(userClaims, "User Identity");  
  36.   
  37.                 var userPrincipal = new ClaimsPrincipal(new[] { userIdentity });  
  38.                 HttpContext.SignInAsync(userPrincipal);  
  39.   
  40.                 return RedirectToAction("Index""Home");  
  41.             }  
  42.   
  43.             return View(user);  
  44.         }  
  45.   
  46.         [HttpGet]  
  47.         public ActionResult UserAccessDenied()  
  48.         {  
  49.             return View();  
  50.         }  
  51.     }  
  52. }  
Add view for UserAccessDenied - Add a new view for the UserAccessDenied method and put the below lines of code into it.
  1. <div class="alert-danger">  
  2.     <h1>Access Denied </h1>  
  3.     <h3>You dont have permission to view this resource.</h3>  
  4. </div>  
Changes In HomeController class - Update HomeController class as I mention. I have updated Authorize tag with Policy And Role.
  1. using CookieAuthenticationDemo.Models;  
  2. using Microsoft.AspNetCore.Authorization;  
  3. using Microsoft.AspNetCore.Mvc;  
  4.   
  5. namespace CookieAuthenticationDemo.Controllers  
  6. {  
  7.     public class HomeController : Controller  
  8.     {  
  9.         public IActionResult Index()  
  10.         {  
  11.             return View();  
  12.         }  
  13.   
  14.         [Authorize]  
  15.         public ActionResult Users()  
  16.         {  
  17.             var uses = new Users();  
  18.             return View(uses.GetUsers());  
  19.         }  
  20.   
  21.          [Authorize(Policy = "UserPolicy")]  
  22.         public ActionResult UsersPolicy()  
  23.         {  
  24.             var uses = new Users();  
  25.             return View("Users", uses.GetUsers());  
  26.         }  
  27.   
  28.         [Authorize(Roles = "User")]  
  29.         public ActionResult UsersRole()  
  30.         {  
  31.             var uses = new Users();  
  32.             return View("Users", uses.GetUsers());  
  33.         }  
  34.          
  35.         [Authorize(Roles = "Admin")]  
  36.         public ActionResult AdminUser()  
  37.         {  
  38.             var uses = new Users();  
  39.             return View("Users", uses.GetUsers());  
  40.         }  
  41.   
  42.     }  
  43. }  
Changes In Startup class - Update Startup class as I mention below. Check the AddPolicy method for how I added claims for user defined policy. Also, I have registered two handler services PoliciesAuthorizationHandler and RolesAuthorizationHandler of IAuthorizationHandler type.
  1. using System.Security.Claims;  
  2. using CookieAuthenticationDemo.CustomHandler;  
  3. using Microsoft.AspNetCore.Authorization;  
  4. using Microsoft.AspNetCore.Builder;  
  5. using Microsoft.AspNetCore.Hosting;  
  6. using Microsoft.Extensions.Configuration;  
  7. using Microsoft.Extensions.DependencyInjection;  
  8. using Microsoft.Extensions.Hosting;  
  9.   
  10. namespace CookieAuthenticationDemo  
  11. {  
  12.     public class Startup  
  13.     {  
  14.         public Startup(IConfiguration configuration)  
  15.         {  
  16.             Configuration = configuration;  
  17.         }  
  18.   
  19.         public IConfiguration Configuration { get; }  
  20.   
  21.         public void ConfigureServices(IServiceCollection services)  
  22.         {  
  23.             services.AddAuthentication("CookieAuthentication")  
  24.                  .AddCookie("CookieAuthentication", config =>  
  25.                  {  
  26.                      config.Cookie.Name = "UserLoginCookie"// Name of cookie     
  27.                      config.LoginPath = "/Login/UserLogin"// Path for the redirect to user login page    
  28.                      config.AccessDeniedPath = "/Login/UserAccessDenied";  
  29.                  });  
  30.   
  31.             services.AddAuthorization(config =>  
  32.             {  
  33.                 config.AddPolicy("UserPolicy", policyBuilder =>  
  34.                 {  
  35.                     policyBuilder.UserRequireCustomClaim(ClaimTypes.Email);  
  36.                     policyBuilder.UserRequireCustomClaim(ClaimTypes.DateOfBirth);  
  37.                 });  
  38.             });  
  39.   
  40.             services.AddScoped<IAuthorizationHandler, PoliciesAuthorizationHandler>();  
  41.             services.AddScoped<IAuthorizationHandler, RolesAuthorizationHandler>();  
  42.   
  43.             services.AddControllersWithViews();  
  44.         }  
  45.   
  46.         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
  47.         {  
  48.             if (env.IsDevelopment())  
  49.             {  
  50.                 app.UseDeveloperExceptionPage();  
  51.             }  
  52.             else  
  53.             {  
  54.                 app.UseExceptionHandler("/Home/Error");  
  55.                 app.UseHsts();  
  56.             }  
  57.             app.UseHttpsRedirection();  
  58.             app.UseStaticFiles();  
  59.   
  60.             app.UseRouting();  
  61.   
  62.             // who are you?  
  63.             app.UseAuthentication();  
  64.   
  65.             // are you allowed?  
  66.             app.UseAuthorization();  
  67.   
  68.             app.UseEndpoints(endpoints =>  
  69.             {  
  70.                 endpoints.MapControllerRoute(  
  71.                     name: "default",  
  72.                     pattern: "{controller=Home}/{action=Index}/{id?}");  
  73.             });  
  74.         }  
  75.     }  
  76. }  
Changes In _Layout.cshtml page - Update _Layout.cshtml as I mention below.  
  1. <!DOCTYPE html>  
  2. <html lang="en">  
  3. <head>  
  4.     <meta charset="utf-8" />  
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />  
  6.     <title>@ViewData["Title"] - CookieAuthenticationDemo</title>  
  7.     <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />  
  8.     <link rel="stylesheet" href="~/css/site.css" />  
  9. </head>  
  10. <body>  
  11.     <header>  
  12.         <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">  
  13.             <div class="container">  
  14.                 <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">My Application</a>  
  15.                 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"  
  16.                         aria-expanded="false" aria-label="Toggle navigation">  
  17.                     <span class="navbar-toggler-icon"></span>  
  18.                 </button>  
  19.                 <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">  
  20.                     <ul class="navbar-nav flex-grow-1">  
  21.                         <li class="nav-item">  
  22.                             <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>  
  23.                         </li>  
  24.                         <li class="nav-item">  
  25.                             <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="UsersPolicy">Users Policy</a>  
  26.                         </li>  
  27.                         <li class="nav-item">  
  28.                             <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="UsersRole">Users Role</a>  
  29.                         </li>  
  30.                     </ul>  
  31.                 </div>  
  32.             </div>  
  33.         </nav>  
  34.     </header>  
  35.     <div class="container">  
  36.         <main role="main" class="pb-3">  
  37.             @RenderBody()  
  38.         </main>  
  39.     </div>  
  40.   
  41.     <footer class="border-top footer text-muted">  
  42.         <div class="container">  
  43.             © 2020 - CookieAuthenticationDemo - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>  
  44.         </div>  
  45.     </footer>  
  46.     <script src="~/lib/jquery/dist/jquery.min.js"></script>  
  47.     <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>  
  48.     <script src="~/js/site.js" asp-append-version="true"></script>  
  49.     @RenderSection("Scripts", required: false)  
  50. </body>  
  51. </html>  
Run your application
 
After successfully running your application, the output of your application should be like the below screen,
 
Policy-Based And Role-Based Authorization In ASP.NET Core 3.0 Using Custom Handler
Click on the UsersPolicy tab to check is whether the user has access here or not.
 
First, it will redirect to the login page for user authentication,
 
Policy-Based And Role-Based Authorization In ASP.NET Core 3.0 Using Custom Handler
Enter Username as "anet" and password "anet" for login. After successfully logging in, now click on the Users Policy tab and check the result. Now the user can easily view information like below:
 
Policy-Based And Role-Based Authorization In ASP.NET Core 3.0 Using Custom Handler
Now click on the Users Role tab. Here, the user cannot access that information and is redirected to the access denied page. I have given  Admin role to the user, and for UserRole action method I have mentioned  User role in Authorization tag,
 
Policy-Based And Role-Based Authorization In ASP.NET Core 3.0 Using Custom Handler
 
Policy-Based And Role-Based Authorization In ASP.NET Core 3.0 Using Custom Handler

Summary

 
In this article, I discussed how to add Custom Policy-based & Role-based Authorization in ASP.NET Core 3.0. We have also created a PoliciesAuthorizationHandler for handling policies and RolesAuthorizationHandler for roles. I hope this will help the readers to understand how to implement the custom authorization handler in any application. Please find the attached code for better understanding.