How to work with Roles in Identity System in ASP.NET Core

How to work with Roles in Identity System in ASP.NET Core

In my previous tutorial I have explained How to do Authentication of Users in Identity in ASP.NET Core. Now I will extend it with Identity Roles.

In Identity System you can create Roles that contain a set of permissions for doing a set of activities in the application. For example an organization can have 4 roles which are:

  • 1. Admin – For doing administration works like assigning work to employees.
  • 2. Managers – For looking after the client needs, and completing projects on time.
  • 3. Network – For keeping the internet of the organization, up and running, in secured manner.
  • 4. Security – For guarding the premises of the organization.

In ASP.NET Core Identity System you can create any number of Roles and assign users to these roles.

For accessing and managing roles you need the help of RoleManager class. T is the class that represents roles in the Identity Database.

To represent roles you will need the help of IdentityRole class. The important properties of this class are:

  • 1. Id – gives a unique identifier of the role.
  • 2. Name – defines the name of the role.
  • 3. Users – It returns a collection of IdentityUserRole objects that represents members of the role.

Creating and Deleting Roles in Identity

I will now create the functionality to Create and Delete Roles in my Identity System. So create a new Controller called ‘RoleController.cs’ and add the following code to it:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace Identity.Controllers
{
    public class RoleController : Controller
    {
        private RoleManager<IdentityRole> roleManager;
        public RoleController(RoleManager<IdentityRole> roleMgr)
        {
            roleManager = roleMgr;
        }

        public ViewResult Index() => View(roleManager.Roles);

        public IActionResult Create() => View();

        [HttpPost]
        public async Task<IActionResult> Create([Required]string name)
        {
            if (ModelState.IsValid)
            {
                IdentityResult result = await roleManager.CreateAsync(new IdentityRole(name));
                if (result.Succeeded)
                    return RedirectToAction("Index");
                else
                    Errors(result);
            }
            return View(name);
        }

        [HttpPost]
        public async Task<IActionResult> Delete(string id)
        {
            IdentityRole role = await roleManager.FindByIdAsync(id);
            if (role != null)
            {
                IdentityResult result = await roleManager.DeleteAsync(role);
                if (result.Succeeded)
                    return RedirectToAction("Index");
                else
                    Errors(result);
            }
            else
                ModelState.AddModelError("", "No role found");
            return View("Index", roleManager.Roles);
        }

        private void Errors(IdentityResult result)
        {
            foreach (IdentityError error in result.Errors)
                ModelState.AddModelError("", error.Description);
        }
    }
}

In the Role Controller class, I have added a dependency of RoleManager class to the Constructor.

The RoleManager class is used to manage the Roles in Identity, and has the some important functions and properties which are defined in the table given below. The ‘role’ is an object of type IdentityRole.

Name Description
CreateAsync(role) Creates a new role
DeleteAsync(role) Deletes the specified role
FindByIdAsync(id) Find a role by its id
FindByNameAsync(name) Find a role by its name
RoleExistsAsync(name) Used to check if a role with the specified name exists or not
UpdateAsync(name) Updates a role
Roles This property returns all the roles in the Identity system
Get all Roles in Identity

The Index Action method of the Role Controller class returns all the Identity Roles to the default View like:

public ViewResult Index() => View(roleManager.Roles);

Create the Index View inside the ‘Views/Role’ folder. Add the following code to it:

@model IEnumerable<IdentityRole>

<h1 class="bg-info text-white">All Roles</h1>
<a asp-action="Create" class="btn btn-secondary">Create a Role</a>

<table class="table table-sm table-bordered table-bordered">
    <tr><th>ID</th><th>Name</th><th>Users</th><th>Update</th><th>Delete</th></tr>
    @foreach (var role in Model)
    {
        <tr>
            <td>@role.Id</td>
            <td>@role.Name</td>
            <td i-role="@role.Id"></td>
            <td><a class="btn btn-sm btn-primary" asp-action="Update" asp-route-id="@role.Id">Update</a></td>
            <td>
                <form asp-action="Delete" asp-route-id="@role.Id" method="post">
                    <button type="submit" class="btn btn-sm btn-danger">
                        Delete
                    </button>
                </form>
            </td>
        </tr>
    }
</table>

This View takes a model of type IEnumerable. I will loop through all the roles, using the foreach method, and show them in the table.

Notice the ‘i-role’ attribute on the 3rd td element:

<td i-role="@role.Id"></td>

This attribute will call a Custom Tag Helper which will modify this td element to display a list of all the Users who are members of each Role.

Create the ‘TagHelper’ folder in the root of the application. To this folder add the Custom Tag Helper and call it ‘RoleUsersTH.cs’. The code of the RoleUserTH class is given below:

using Identity.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Identity.TagHelpers
{
    [HtmlTargetElement("td", Attributes = "i-role")]  
    public class RoleUsersTH : TagHelper
    {
        private UserManager<AppUser> userManager;
        private RoleManager<IdentityRole> roleManager;

        public RoleUsersTH(UserManager<AppUser> usermgr, RoleManager<IdentityRole> rolemgr)
        {
            userManager = usermgr;
            roleManager = rolemgr;
        }

        [HtmlAttributeName("i-role")]
        public string Role { get; set; }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            List<string> names = new List<string>();
            IdentityRole role = await roleManager.FindByIdAsync(Role);
            if (role != null)
            {
                foreach (var user in userManager.Users)
                {
                    if (user != null && await userManager.IsInRoleAsync(user, role.Name))
                        names.Add(user.UserName);
                }
            }
            output.Content.SetContent(names.Count == 0 ? "No Users" : string.Join(", ", names));
        }
    }
}

This tag helper operates on td elements with an ‘i-role’ attribute, which is used to receive the
id of the role that is being processed. The RoleManager and UserManager objects fetches a list of all the users that resides in a given role.
I need to update my view imports file (_ViewImports.cshtml) so that:

1. I can refer to the ‘Microsoft.AspNetCore.Identity’ namespace in the views without using a namespace.
2. I can use my custom tag helper in the Views.

The updated code of the ‘_ViewImports.cshtml’ file is given below:

@using Identity.Models
@using Microsoft.AspNetCore.Identity
@addTagHelper Identity.TagHelpers.*, Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Creating a New Role

The new Role is created by the Create Action method whose code is:

[HttpPost]
public async Task<IActionResult> Create([Required]string name)
{
    if (ModelState.IsValid)
    {
        IdentityResult result = await roleManager.CreateAsync(new IdentityRole(name));
        if (result.Succeeded)
            return RedirectToAction("Index");
        else
            Errors(result);
    }
    return View(name);
}

It takes the Role name from an Input control in the View and uses the CreateAsync method to create the View.

Add a new View called ‘Create’ inside the ‘Views/Role’ folder with the code as shown below:

@model string

<h1 class="bg-info text-white">Create a Role</h1>
<a asp-action="Index" class="btn btn-secondary">Back</a>
<div asp-validation-summary="All" class="text-danger"></div>

<form asp-action="Create" method="post">
    <div class="form-group">
        <label for="name">Name:</label>
        <input name="name" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
</form>

Deleting a Role

The Delete Role feature is provided by the Delete Action method which takes the Role id in its parameter, and uses the DeleteAsync method to delete the role. The Code of this action method is given below:

[HttpPost]
public async Task<IActionResult> Delete(string id)
{
    IdentityRole role = await roleManager.FindByIdAsync(id);
    if (role != null)
    {
        IdentityResult result = await roleManager.DeleteAsync(role);
        if (result.Succeeded)
            return RedirectToAction("Index");
        else
            Errors(result);
    }
    else
        ModelState.AddModelError("", "No role found");
    return View("Index", roleManager.Roles);
}

Testing the Create & Delete Functionality

Run your project and go to the create role URL – ‘/Role/Create’. Once you create a role you will be redirected the Index View, which will show all the Roles in your Identity System.

The create role view is shown below:

create role identity

The Index View will have a delete button, through which a role can be deleted from Identity System, see the below image:

all roles identity

Adding and Removing Users from Roles in Identity

Now I will create a functionality to add or remove users from Roles. For implementing this functionality add 2 classes called ‘RoleEdit.cs’ and ‘RoleModification.cs’ in the ‘Models’ folder.

The RoleEdit class code:

using Microsoft.AspNetCore.Identity;
using System.Collections.Generic;

namespace Identity.Models
{
    public class RoleEdit
    {
        public IdentityRole Role { get; set; }
        public IEnumerable<AppUser> Members { get; set; }
        public IEnumerable<AppUser> NonMembers { get; set; }
    }
}

The RoleEdit class is used to represent the Role and the details of the Users in the Identity System.

The RoleModification class code:

using System.ComponentModel.DataAnnotations;

namespace Identity.Models
{
    public class RoleModification
    {
        [Required]
        public string RoleName { get; set; }
        public string RoleId { get; set; }
        public string[] AddIds { get; set; }
        public string[] DeleteIds { get; set; }
    }
}

The RoleModification class code represents the changes that will be done to a role.

I will now use these 2 classes to add or remove users from a role. For this I will add ‘Update’ action method to the Role Controller.

The Updated controller code with the Update Action methods is given below:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Identity.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace Identity.Controllers
{
    public class RoleController : Controller
    {
        private RoleManager<IdentityRole> roleManager;
        private UserManager<AppUser> userManager;

        public RoleController(RoleManager<IdentityRole> roleMgr, UserManager<AppUser> userMrg)
        {
            roleManager = roleMgr;
            userManager = userMrg;
        }

        // removed for clarity

        public async Task<IActionResult> Update(string id)
        {
            IdentityRole role = await roleManager.FindByIdAsync(id);
            List<AppUser> members = new List<AppUser>();
            List<AppUser> nonMembers = new List<AppUser>();
            foreach (AppUser user in userManager.Users)
            {
                var list = await userManager.IsInRoleAsync(user, role.Name) ? members : nonMembers;
                list.Add(user);
            }
            return View(new RoleEdit
            {
                Role = role,
                Members = members,
                NonMembers = nonMembers
            });
        }

        [HttpPost]
        public async Task<IActionResult> Update(RoleModification model)
        {
            IdentityResult result;
            if (ModelState.IsValid)
            {
                foreach (string userId in model.AddIds ?? new string[] { })
                {
                    AppUser user = await userManager.FindByIdAsync(userId);
                    if (user != null)
                    {
                        result = await userManager.AddToRoleAsync(user, model.RoleName);
                        if (!result.Succeeded)
                            Errors(result);
                    }
                }
                foreach (string userId in model.DeleteIds ?? new string[] { })
                {
                    AppUser user = await userManager.FindByIdAsync(userId);
                    if (user != null)
                    {
                        result = await userManager.RemoveFromRoleAsync(user, model.RoleName);
                        if (!result.Succeeded)
                            Errors(result);
                    }
                }
            }

            if (ModelState.IsValid)
                return RedirectToAction(nameof(Index));
            else
                return await Update(model.RoleId);
        }

        private void Errors(IdentityResult result)
        {
            foreach (IdentityError error in result.Errors)
                ModelState.AddModelError("", error.Description);
        }
    }
}

Notice I have added a dependency of UserManager class on the constructor.

The Get version of the Update Action method is used to generate members and non-members of a selected role. While the Post version of the Update Action method is used for adding or removing users from a role.

I have used the following members of the UserManager class to play with the roles:

Name Description
AddToRoleAsync(AppUser user, string name) Adds a user to a role
RemoveFromRoleAsync(AppUser user, string name) Removes a user from a role
GetRolesAsync(AppUser user) Gives the names of the roles in which the user is a member
IsInRoleAsync(AppUser user, string name) Returns true is the user is a member of a specified role, else returns false

Next, add an ‘Update’ View inside the ‘Views/Role’ folder with the following code:

@model RoleEdit

<h1 class="bg-info text-white">Update Role</h1>
<a asp-action="Index" class="btn btn-secondary">Back</a>
<div asp-validation-summary="All" class="text-danger"></div>

<form method="post">
    <input type="hidden" name="roleName" value="@Model.Role.Name" />
    <input type="hidden" name="roleId" value="@Model.Role.Id" />
    
    <h2 class="bg-info p-1 text-white">Add To @Model.Role.Name</h2>
    <table class="table table-bordered table-sm">
        @if (Model.NonMembers.Count() == 0)
        {
            <tr><td colspan="2">All Users Are Members</td></tr>
        }
        else
        {
            @foreach (AppUser user in Model.NonMembers)
            {
                <tr>
                    <td>@user.UserName</td>
                    <td>
                        <input type="checkbox" name="AddIds" value="@user.Id">
                    </td>
                </tr>
            }
        }
    </table>

    <h2 class="bg-info p-1 text-white">Remove From @Model.Role.Name</h2>
    <table class="table table-bordered table-sm">
        @if (Model.Members.Count() == 0)
        {
            <tr><td colspan="2">No Users Are Members</td></tr>
        }
        else
        {
            @foreach (AppUser user in Model.Members)
            {
                <tr>
                    <td>@user.UserName</td>
                    <td>
                        <input type="checkbox" name="DeleteIds" value="@user.Id">
                    </td>
                </tr>
            }
        }
    </table>
    <button type="submit" class="btn btn-primary">Save</button>
</form>

The view contains 2 tables:

1. For users who are ‘non-members’ of the selected role.
2. For users who are ‘members’ of the selected role.

A checkbox is given against each user’s name, and through this checkbox the users can be added or removed from the selected role.

Testing Update Role Feature

Make sure you have 3 Users created in Identity. If not, then go to the URL – ‘/Admin/Create’ and create the following users:

1. Name – tom, Email – [email protected], Password – [email protected]
2. Name – alice, Email – [email protected], Password – [email protected]
3. Name – pintu, Email – [email protected], Password – [email protected]

See the below image:

create users in identity

To test the Update Role Feature, where you will add or remove users from a role, you should have 3 roles created from the URL – ‘/Role/Create’.

These roles are:

1. Managers
2. Developers
3. Admin

Notice the ‘Users’ column shows ‘No Users’ for each Role, as shown in the image below:

create roles in identity

Now click the ‘Update’ button for the Manager’s role. You will be taken to the Update View where you will see your 3 users under the ‘Add To Manager’ heading, see below image:

update role

Next, click ‘tom’ and ‘alice’ checkboxes and click the save button. This will add these 2 users to the ‘Manager’ role, as shown by the given image:

users added to role

Next, click the ‘Update’ button of the Manager’s role once more. You will now see user ‘pintu’ shown inside the heading ‘Add To Manager’, and ‘tom’ & ‘alice’ shown inside another heading ‘Remove From Manager’.

Check the below image:

members and non members of manager role

Click alice checkbox and then click the save button. This will remove ‘alice’ from ‘Mangers’ role. Check the below image:

alice removed from manager role

Note that a user can be added to multiple Roles, like tom can be added to all the roles of the Identity System.

Roles for Authentication

Roles can be use with the [[Authorize]] attributes to specify that all Users of the selected Role can only access the Action method.

Add the Roles property of the ‘Authoriz’e attribute to specify that only ‘Manager’ role users can access the Index Action method of the Home Controller:

public class HomeController : Controller
{
    [Authorize(Roles = "Manager")]
    public IActionResult Index()
    {
        return View((object)"Hello");
    }
}

Run your application and login with user’s ‘tom’ credentials. Everything will work fine since user ‘tom’ belongs to the ‘Manager’ role.

If you had logged in with user’s ‘alice’ credentials then you will not be redirected to the Index Action of the Home Controller. This is due to the fact that ‘alice’ does not belong to the role of ‘Manager.

You will instead be redirected to the access denied URL – ‘Account/AccessDenied’.

Create ‘AccessDenied’ Action in the Account Controller with the following code:

public IActionResult AccessDenied()
{
    return View();
}	

Also adds the AccessDenied.cshtml view inside the ‘Views/Account’ folder:

<h2 class="bg-danger mb-1 p-2 text-white">Access Denied</h2>
<a asp-action="Index" asp-controller="Home" class="btn btn-primary">OK</a>

Now re-run your application and go to the login URL – ‘/Account/Login’. Log in with ‘alice’ credentials:
1. Email – [email protected]
2. Password – [email protected]

After login you will be redirected to the Access Denied URL, as shown in the below image:

access denied url identity

The ‘/Account/AccessDenied’ URL is the default URL set by Identity which you can change by setting the AccessDeniedPath configuration property in the ConfigureServices method of Statup Class.

Remember: If you change the roles for the user you are currently authenticated as, the changes won’t take effect until you log out and authenticate.

You can download the full codes of this tutorial from the below link:

Download

Conclusion

This completes the tutorial on Roles in Identity and now you are in a position to upgrade your website to include Identity Roles and authentication of users based on these roles.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "WordPress, SEO, jQuery, HTML" and more.