Skip to main content

Dotnet Core MVC Cookie Login Sample Role-based Authorization (Part 2)

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.
😊

Refer:

Comments