Introduction:
Authorization means an authenticated user having permission to access specific protected resources. In general, Authorization can be called special permissions. For example in an application, an admin user has all permission to modify the application, a non-admin user might have permission likes read-only content in the application.
In the first article, we have discussed the Cookie-Based login mechanism. This will be a continuation article, here we discuss Authorization.
Note: Before reading this article, read Dotnet Core Cookie Login Sample (Part 1).Pages Controller:
Now add a new controller name as 'PagesController.cs' and to this controller add 3-action methods to show 3 pages in the application.
PagesController.cs:namespace CookieAuth.Web.Controllers { [Route("pages")] public class PagesController : Controller { [Route("admin")] [HttpGet] public IActionResult Admin() { return View(); } [Route("viewer")] [HttpGet] public IActionResult Viewer() { return View(); } [Route("guest")] [HttpGet] public IActionResult Guest() { return View(); } } }In 'view' folder, add 'Pages' folder in that add 'admin.cshtml', 'viewer.cshtml' and 'guest.cshtml'.
Update Menu With New Page Links:
In '_Layout.cshtml' page update the menu links with new 'PagesController' action method routes
Now run the application and see the menu looks as below
Add Authorization Filter:
Now add Authorization attribute filter to "admin" and "view" action methods in 'PagesController'
Now run the application and try to access either admin or viewer page, we are automatically redirecting to the login page. Because simple authorization attribute checks for a user is authenticated or not. If the user did not authenticate the authorization filter blocks the user from accessing resources.
Once the user authenticated, the user can able access both "admin" and "viewer" pages. Now based on User Roles we are going to restrict like admin users can access all the pages, viewer users can access viewer page but not admin pages. We do this by using ASP.NET Core Role-based authorization.
Update Roles Table:
Now add few user roles like 'admin', 'viewer' into the roles table
Update UserRole Table:
Now map the User and Roles tables by inserting their Id's into the UserRole Table
Role-based Authorization:
ASP.NET Core has provided a rich variety of ways to implement Authorization like 'Role-based Authorization', 'Claims-based Authorization', 'Policy-based Authorization', etc. We are going to use Role-based Authorization in this sample. An authenticated user to access or deny a protected resource based on his roles and permissions that can be configured with Role-based Authorization in ASP.NET Core.
Update Login Action To Add Roles Into Login Cookie:
Now we need to get the user roles and add them as claims to the ASP.NET Sign-In Context.
Now update the 'Login' action method 'AccountController' as below.
[Route("login")] [HttpPost] public async Task<IActionResult> Login(LoginViewModel viewModel) { if (ModelState.IsValid) { // note : real time we save password with encryption into the database // so to check that viewModel.Password also need to encrypt with same algorithm // and then that encrypted password value need compare with database password value Models.User user = _userContext.User.Where(_ => _.Email.ToLower() == viewModel.Email.ToLower() && _.Password == viewModel.Password).FirstOrDefault(); if (user != null) { user.LastLoginTime = DateTime.Now; _userContext.SaveChanges(); var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.Email), new Claim("FirstName",user.FirstName), }; var userRoles = _userContext.UserRole.Join( _userContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => new { ur.RoleId, r.RoleName, ur.UserId }).Where(_ => _.UserId == user.Id).ToList(); foreach (var ur in userRoles) { var roleClaim = new Claim(ClaimTypes.Role, ur.RoleName); claims.Add(roleClaim); } var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties() { IsPersistent = viewModel.IsPersistant }; await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); return Redirect("/"); } else { ModelState.AddModelError("InvalidCredentials", "Either username or password is not correct"); } } return View(viewModel); }. Linq to Join UseRole and Role table and finally filter the data by current UserId in UserRole table and get the collection of roles related to the user.
. Create 'Claim' instances for all the roles of the user. Overloaded Claim constructor expecting two parameters, one is a type (any name) but for adding roles to the claim we alway pass type name as 'System.Security.Claims.ClaimType.Role' and the second parameter, here we have to pass our role name.
Now we have successfully added our roles to login user context.
Update Authorization Filters In PagesController:
To Role-based Authorization filter, we need to pass the role as an input parameter to the attributes.
[Authorize(Role="RoleName")]
If the resource needs to be accessed by multiple role users then role names are passed as a parameter but separate ','
[Authorize(Role="RoleNaem1, RoleName2")]
Update 'Admin' action method in 'PagesController':
[Route("admin")] [HttpGet] [Authorize(Roles = "Admin")] public IActionResult Admin() { return View(); }Here this 'admin' page is only accessed by users having role 'Admin'.
Update 'Viewer' action method in 'PagesController'
[Route("viewer")] [HttpGet] [Authorize(Roles = "Admin, Viewer")] public IActionResult Viewer() { return View(); }Here this 'viewer' page can be accessed by users having a role either 'Viewer' or 'Admin'.
Test Our Sample Application:
Now run the application, for testing purpose create 2 users and one user assign role 'admin' and another user assign 'viewer'. To do this mapping add the records into 'UserRole' table as shown in the image above steps
Now login to the application as 'Admin' role user:
Navigate to admin page "https://localhost:44318/pages/admin"
Now navigate to the viewer page "https://localhost:44318/pages/viewer"
This show our admin has the ability to access both the pages
Now logout of application, and log in as the user who is having 'Viewer' Role
Navigate to viewer page "https://localhost:44318/pages/viewer"
Now try to navigate to the admin page "https://localhost:44318/pages/admin"
If we observe we are redirected to URL 'access-denied' since we didn't create any 404 not found page it showing a general error. For you, production deployment creates 404 with that access denied URL.
It shows how Role-based Authorization works and it is very easy to understand and simple to implement.
Summary:
In Part 1 we implement Cookie-based Authentication, now we have implemented Role-based Authorization. By understanding this approach we can understand that ASP.NET Core has given a lot of flexibility to implement Authentication and Authorization very simple and easy way without any Login Library.
😊
Thanks for this excellent post. I got a question, once that the user is authenticated, is there an easy way to get his user roles from the HttpContext?
ReplyDeleteThanks for this
ReplyDeletehow do i display the role name of the logged in user in layout after login
ReplyDeletehi . naveen, still my authorization is not working. [Athorize (Roles="Administrator")] and i am login in as john with role Administrator,, the error is this localhost page cant be found, no web page was found for the web address,,, the same error as above
ReplyDeletehi . naveen,I am using visual studio 2022,, .net 6, still my authorization is not working. [Athorize (Roles="Administrator")] and i am login in as john with role Administrator,, the error is this localhost page cant be found, no web page was found for the web address,,, the same error as above
DeleteREPLY