Manage Role Claims in ASP.NET Core Identity

Manage Role Claims in ASP.NET Core Identity

In this article, I will discuss Managing Role Claims, i.e., How to Add or Remove Role Claims in ASP.NET Core Identity. Please read our previous article discussing how to Manage User Claims in ASP.NET Core Identity.

In ASP.NET Core Identity, claims can be assigned not only to users but also to roles. Role Claims simplify access control by attaching permissions at the role level instead of assigning them individually to every user. When a user is part of a role, they automatically inherit the claims assigned to that role. This makes role-based claims management an efficient way to enforce consistent security policies across groups of users.

Pages to Update and Create:

Let us proceed and understand how to Manage Role Claims in our existing application. We will update a few existing pages and add a new page for Role claims management. Let us first see the pages to be developed or enhanced.

Role Index Page (Existing Page):

Now, we need to enhance the Role Index Page to include a link to Manage the Role Claims. So, the updated Role Index Page will look like the one below.

Manage Role Claims in ASP.NET Core Identity

User Details Page (Existing Page):

Now, we need to enhance the Role Details Page to include a section to display all the assigned Role Claims. So, the updated Role Details Page will look like the one below.

How to Add or Remove Role Claims in ASP.NET Core Identity

Manage Role Claims Page (New Page):

Now, we will create a new Page to manage the Role Claims. From this page, we can assign new claims and remove existing claims for a role.

How to Manage Role Claims in ASP.NET Core Identity

Adding and Updating ViewModels

Let us proceed and add/update the view models to manage the role claims.

Modify RoleDetailsViewModel

Add a new property to display assigned role claims in the Role Details page. So, please modify the RoleDetailsViewModel.cs class file, which is available inside ViewModels/Roles folder, as follows. The RoleDetailsViewModel holds the full role data for the details page.

namespace ASPNETCoreIdentityDemo.ViewModels.Roles
{
    public class RoleDetailsViewModel
    {
        public Guid Id { get; init; }
        public string Name { get; init; } = string.Empty;
        public string? Description { get; init; }
        public bool IsActive { get; init; }
        public DateTime? CreatedOn { get; init; }
        public DateTime? ModifiedOn { get; init; }
        public PagedResult<UserInRoleViewModel> Users { get; init; } = new();

        // Assigned role claims, e.g., "Permission: ViewUsers"
        public List<string> Claims { get; init; } = new();
    }
}
RoleClaimCheckboxItem

Create a class file named RoleClaimCheckboxItem.cs within the ViewModels/Roles folder, then copy and paste the following code. The RoleClaimCheckboxItem is a lightweight DTO for the Manage Claims UI; it represents one claim row (id, type, value, category, description) and whether it’s currently selected for the role.

namespace ASPNETCoreIdentityDemo.ViewModels.Roles
{
    public class RoleClaimCheckboxItem
    {
        public Guid ClaimId { get; set; }         // Primary key of ClaimMaster
        public string ClaimType { get; set; } = string.Empty; // e.g., "Permission"
        public string ClaimValue { get; set; } = string.Empty; // e.g., "ViewUsers"
        public string Category { get; set; } = string.Empty;  //Role, Both
        public string? Description { get; set; }               // Optional admin help text
        public bool IsSelected { get; set; }                   // Whether the user currently has it
    }
}
RoleClaimsEditViewModel

Create a class file named RoleClaimsEditViewModel.cs within the ViewModels/Roles folder, then copy and paste the following code. The RoleClaimsEditViewModel is the form model for the Manage Role Claims page; it carries the target RoleId, Name, and the collection of RoleClaimCheckboxItem entries to render and post back.

using System.ComponentModel.DataAnnotations;

namespace ASPNETCoreIdentityDemo.ViewModels.Roles
{
    public class RoleClaimsEditViewModel
    {
        [Required(ErrorMessage = "Invalid Role.")]
        public Guid RoleId { get; set; }

        [Display(Name = "Role Name")]
        public string RoleName { get; set; } = string.Empty;

        // Only claims from Category = Role or Both (and active)
        public List<RoleClaimCheckboxItem> Claims { get; set; } = new();
    }
}
Service interface

The IRoleService interface is extended with two methods, GetClaimsForEditAsync and UpdateClaimsAsync, to build the claims editor view model and to persist changes to the user’s claims. So, please modify the IRoleService interfaces as follows.

using ASPNETCoreIdentityDemo.ViewModels;
using ASPNETCoreIdentityDemo.ViewModels.Roles;
using Microsoft.AspNetCore.Identity;

namespace ASPNETCoreIdentityDemo.Services
{
    public interface IRoleService
    {
        Task<PagedResult<RoleListItemViewModel>> GetRolesAsync(RoleListFilterViewModel filter);
        Task<(IdentityResult Result, Guid? RoleId)> CreateAsync(RoleCreateViewModel model);
        Task<RoleEditViewModel?> GetForEditAsync(Guid id);
        Task<IdentityResult> UpdateAsync(RoleEditViewModel model);
        Task<IdentityResult> DeleteAsync(Guid id);
        Task<RoleDetailsViewModel?> GetDetailsAsync(Guid id, int pageNumber, int pageSize);

        //New methods for Role Claims Management
        Task<RoleClaimsEditViewModel?> GetClaimsForEditAsync(Guid roleId);
        Task<IdentityResult> UpdateClaimsAsync(Guid roleId, IEnumerable<Guid> selectedClaimIds);
    }
}
Service implementation

Open the Services/RoleService.cs class file and add the following two methods. These two methods are used to manage the Role Claims.

GetClaimsForEditAsync:

The GetClaimsForEditAsync(…) method queries active ClaimMasters limited to categories Role/Both, reads the role’s current claims, and returns a RoleClaimsEditViewModel with items pre-checked where the role already has them.

public async Task<RoleClaimsEditViewModel?> GetClaimsForEditAsync(Guid roleId)
{
    var role = await _roleManager.Roles.AsNoTracking().FirstOrDefaultAsync(r => r.Id == roleId);
    if (role == null) return null;

    // Allowed options: Active + (Role or Both) from ClaimMasters
    var allClaims = await _dbContext.ClaimMasters.AsNoTracking()
        .Where(c => c.IsActive && (c.Category == "Role" || c.Category == "Both"))
        .OrderBy(c => c.ClaimType).ThenBy(c => c.ClaimValue)
        .ToListAsync();

    var current = await _roleManager.GetClaimsAsync(role);

    return new RoleClaimsEditViewModel
    {
        RoleId = role.Id,
        RoleName = role.Name ?? "",
        Claims = allClaims.Select(c => new RoleClaimCheckboxItem
        {
            ClaimId = c.Id,
            ClaimType = c.ClaimType,
            ClaimValue = c.ClaimValue,
            Category = c.Category,
            Description = c.Description,
            IsSelected = current.Any(rc => rc.Type == c.ClaimType && rc.Value == c.ClaimValue)
        }).ToList()
    };
}
UpdateClaimsAsync:

The UpdateClaimsAsync(…) method validates selected claim IDs against allowed ClaimMasters, removes existing role claims, and adds the newly selected claims via RoleManager.

public async Task<IdentityResult> UpdateClaimsAsync(Guid roleId, IEnumerable<Guid> selectedClaimIds)
{
    var role = await _roleManager.FindByIdAsync(roleId.ToString());
    if (role == null)
        return IdentityResult.Failed(new IdentityError { Code = "RoleNotFound", Description = "Role not found." });

    // Only accept active Role/Both claims
    var allowed = await _dbContext.ClaimMasters
        .Where(c => c.IsActive && (c.Category == "Role" || c.Category == "Both"))
        .ToListAsync();

    var selected = allowed.Where(c => selectedClaimIds.Contains(c.Id)).ToList();

    // Replace all current role claims (simple, matches user-claims flow)
    var current = await _roleManager.GetClaimsAsync(role);
    foreach (var c in current)
    {
        var rm = await _roleManager.RemoveClaimAsync(role, c);
        if (!rm.Succeeded) return rm;
    }

    foreach (var c in selected)
    {
        var add = await _roleManager.AddClaimAsync(role, new Claim(c.ClaimType, c.ClaimValue));
        if (!add.Succeeded) return add;
    }

    return IdentityResult.Success;
}
Importing Namespace:

Please import the following namespace.

Using System.Security.Claims;

Modify the GetDetailsAsync method of the Role Service class:

Please modify the GetDetailsAsync method of the Role Service class as follows to return the assigned claims of the role.

public async Task<RoleDetailsViewModel?> GetDetailsAsync(Guid id, int pageNumber, int pageSize)
{
    var role = await _roleManager.Roles.AsNoTracking().FirstOrDefaultAsync(r => r.Id == id);
    if (role == null)
        return null;

    // Query all users in this role via junction table (IdentityUserRole)
    var usersQuery =
        from ur in _dbContext.Set<IdentityUserRole<Guid>>().AsNoTracking() //Left table - User Roles
        join u in _dbContext.Set<ApplicationUser>().AsNoTracking() //Right table - Users
        on ur.UserId equals u.Id
        where ur.RoleId == id
        select new UserInRoleViewModel
        {
            Id = u.Id,
            Email = u.Email!,
            FirstName = u.FirstName,
            LastName = u.LastName,
            IsActive = u.IsActive,
            PhoneNumber = u.PhoneNumber
        };

    // Get total user count
    var total = await usersQuery.CountAsync();

    // Get current page of users
    var users = await usersQuery
        .OrderBy(u => u.Email)
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();

    //fetch the role claims
    var claims = await _roleManager.GetClaimsAsync(role);
    var claimTexts = claims
        .OrderBy(c => c.Type).ThenBy(c => c.Value)
        .Select(c => $"{c.Type}: {c.Value}")
        .ToList();

    // Return role details with users
    return new RoleDetailsViewModel
    {
        Id = role.Id,
        Name = role.Name ?? string.Empty,
        Description = role.Description,
        IsActive = role.IsActive,
        CreatedOn = role.CreatedOn,
        ModifiedOn = role.ModifiedOn,
        Claims = claimTexts, //Populate the Role Claims in the RoleDetailsViewModel
        Users = new PagedResult<UserInRoleViewModel>
        {
            Items = users,
            TotalCount = total,
            PageNumber = pageNumber,
            PageSize = pageSize
        }
    };
}
Modifying RolesController

Please add the following two methods to the Roles Controller to manage role claims.

ManageClaims (GET):

This method loads the claims editor view model for a given role; if the role isn’t found, it sets an error and redirects back to the index.

[Authorize(Roles = "Admin")]
[HttpGet]
public async Task<IActionResult> ManageClaims(Guid id)
{
    var roleClaimsEditViewModel = await _roleService.GetClaimsForEditAsync(id);
    if (roleClaimsEditViewModel == null)
    {
        TempData["Error"] = "The role was not found.";
        return RedirectToAction(nameof(Index));
    }
    return View(roleClaimsEditViewModel);
}
ManageClaims (POST):

This method accepts the posted RoleClaimsEditViewModel, extracts the selected claims, calls the service method to update, and then shows success or validation errors before redirecting to the role’s details page.

[Authorize(Roles = "Admin")]
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> ManageClaims(RoleClaimsEditViewModel model)
{
    if (!ModelState.IsValid)
        return View(model);

    try
    {
        var selected = model.Claims.Where(c => c.IsSelected).Select(c => c.ClaimId).ToList();
        var result = await _roleService.UpdateClaimsAsync(model.RoleId, selected);

        if (result.Succeeded)
        {
            TempData["Success"] = "Role claims were updated successfully.";
            return RedirectToAction(nameof(Details), new { id = model.RoleId });
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }

        var reload = await _roleService.GetClaimsForEditAsync(model.RoleId);
        return View(reload ?? model);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, $"Error in Managing Role Claims. RoleName: {model.RoleName}");
        return View("Error");
    }
}
Creating and Updating Views:

We will update several existing role-related views and create a new view to manage Role claims in ASP.NET Core Identity.

Roles/Index.cshtml — Add Manage Claims Button:

Please modify the Role Index view as follows. It enhances the roles listing page by adding a Claims action button (next to existing actions) so admins can jump directly to Manage Claims for any role.

@using ASPNETCoreIdentityDemo.ViewModels
@using ASPNETCoreIdentityDemo.ViewModels.Roles
@model PagedResult<RoleListItemViewModel>
@{
ViewData["Title"] = "Roles";
var filter = (RoleListFilterViewModel)ViewBag.Filter;
}
<div class="container mt-1">
<!-- Page Header -->
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4 pb-2 border-bottom">
<div>
<h1 class="h3 mb-1 fw-bold text-primary">Roles Administration</h1>
<p class="text-muted mb-0">
Manage application roles, update permissions, and review assigned users.
</p>
</div>
<div>
<a asp-action="Create" class="btn btn-primary">
<i class="bi bi-plus-lg me-1"></i>Create New Role
</a>
</div>
</div>
<!-- Alerts -->
@if (TempData["Success"] is string sMsg)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle me-2"></i>@sMsg
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
@if (TempData["Error"] is string eMsg)
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>@eMsg
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
<!-- Filter Bar -->
<form method="get" asp-action="Index" asp-controller="Roles" class="row g-2 align-items-end mb-3">
<div class="col-md-4">
<label class="form-label">Search</label>
<input name="Search" value="@filter.Search" class="form-control"
placeholder="Search by name or description..." />
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select name="IsActive" class="form-select">
<option value="">All Status</option>
<option value="true" selected="@(filter.IsActive == true)">Active</option>
<option value="false" selected="@(filter.IsActive == false)">Inactive</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Page Size</label>
<select name="PageSize" class="form-select">
@foreach (var size in new[] { 5, 10, 20, 50 })
{
<option value="@size" selected="@(filter.PageSize == size)">@size / page</option>
}
</select>
</div>
<div class="col-md-2 d-grid d-sm-flex gap-2">
<button class="btn btn-primary" type="submit">
<i class="bi bi-funnel me-1"></i> Apply
</button>
<!-- Clear = go to Index without query string -->
<a asp-action="Index" class="btn btn-info">
<i class="bi bi-x-circle me-1"></i> Clear
</a>
</div>
</form>
<!-- Table / Empty state -->
@if (Model.Items.Any())
{
<div class="card shadow border-0 rounded">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table align-middle mb-0">
<thead class="table-dark">
<tr>
<th>Name</th>
<th>Description</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var role in Model.Items)
{
var desc = role.Description ?? "";
var shortDesc = desc.Length > 80 ? desc.Substring(0, 77) + "..." : desc;
<tr>
<td class="fw-semibold">@role.Name</td>
<td>
<span data-bs-toggle="tooltip" title="@desc">@shortDesc</span>
</td>
<td>
@if (role.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-danger">Inactive</span>
}
</td>
<td>@(role.CreatedOn?.ToString("yyyy-MM-dd") ?? "-")</td>
<td class="text-end">
<a asp-action="Details" asp-route-id="@role.Id"
class="btn btn-sm btn-outline-info me-2">Details
</a>
<a asp-action="Edit" asp-route-id="@role.Id"
class="btn btn-sm btn-outline-primary me-2">Edit
</a>
<a asp-action="ManageClaims" asp-route-id="@role.Id" class="btn btn-sm btn-outline-primary">
Manage Claims
</a>
<button class="btn btn-sm btn-outline-danger"
type="button"
data-bs-toggle="modal"
data-bs-target="#confirmDeleteModal"
data-role-id="@role.Id"
data-role-name="@role.Name">
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
<!-- Pager -->
<partial name="~/Views/Shared/_Pager.cshtml"
model="new PagedResult<object> { Items = Array.Empty<object>(), TotalCount = Model.TotalCount, PageNumber = Model.PageNumber, PageSize = Model.PageSize }" />
}
else
{
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5">
<div class="display-6 mb-2">No roles to display</div>
<p class="text-muted mb-4">Try adjusting filters or create your first role.</p>
<a asp-action="Create" class="btn btn-primary">
<i class="bi bi-plus-lg me-1"></i>Create Role
</a>
</div>
</div>
}
</div>
<!-- Delete Confirmation Modal (centered) -->
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header">
<h5 class="modal-title" id="confirmDeleteLabel">Delete Role</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to delete role <strong id="delRoleName"></strong>?
<div class="text-muted small mt-2">This action cannot be undone.</div>
</div>
<div class="modal-footer">
<form id="delForm" method="post" asp-action="Delete" asp-controller="Roles">
@Html.AntiForgeryToken()
<input type="hidden" name="id" id="delId" />
<button type="button" class="btn btn-info" data-bs-dismiss="modal">Cancel</button>
<button class="btn btn-danger" type="submit">Delete</button>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script>
$(function () {
// Enable Bootstrap tooltips
$('[data-bs-toggle="tooltip"]').tooltip();
// Show delete modal with populated role info
$('#confirmDeleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var roleId = button.data('role-id');
var roleName = button.data('role-name');
$('#delRoleName').text(roleName || '');
$('#delId').val(roleId || '');
});
});
</script>
}
Roles/Details.cshtml — Render the Role Claims section below the Users

Right after the existing user section, we need to add the Claim section. Please modify the Role Details view as follows.

@using ASPNETCoreIdentityDemo.ViewModels
@using ASPNETCoreIdentityDemo.ViewModels.Roles
@model RoleDetailsViewModel
@{
ViewData["Title"] = "Role Details";
var totalUsers = Model.Users.TotalCount;
var totalClaims = Model.Claims?.Count;
var start = totalUsers == 0 ? 0 : ((Model.Users.PageNumber - 1) * Model.Users.PageSize) + 1;
var end = Math.Min(Model.Users.PageNumber * Model.Users.PageSize, totalUsers);
string FormatDate(DateTime? dt) => dt.HasValue ? dt.Value.ToString("dd MMM yyyy, HH:mm") : "-";
}
<div class="container mt-1">
<!-- Page Header -->
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4 pb-2 border-bottom">
<div>
<h1 class="h3 mb-1 fw-bold text-primary">
Role: @Model.Name
</h1>
<p class="text-muted mb-0">
Role details, assigned users, and claims.
</p>
</div>
<div class="d-flex gap-2">
<a asp-controller="Roles" asp-action="Index" class="btn btn-info">
<i class="bi bi-arrow-left me-1"></i> Back to Roles
</a>
<a asp-controller="Roles" asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-success">
<i class="bi bi-pencil-square me-1"></i> Edit Role
</a>
</div>
</div>
<div class="row g-3">
<!-- LEFT: Role Overview -->
<div class="col-lg-5">
<div class="card shadow-sm border-0 h-100">
<div class="card-body">
<h5 class="card-title mb-2">Role Overview</h5>
<div class="mb-1">
<div class="fw-semibold text-muted small">Description</div>
<div>@(string.IsNullOrWhiteSpace(Model.Description) ? "No description provided." : Model.Description)</div>
</div>
<div class="mb-1">
<div class="fw-semibold text-muted small">Status</div>
@if (Model.IsActive)
{
<span class="badge bg-success">Active</span>
<span class="text-muted ms-2 small">This role can be assigned to users.</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
<span class="text-muted ms-2 small">This role is currently disabled.</span>
}
</div>
<div class="row g-3">
<div class="col-6">
<div class="border rounded-3 p-3">
<div class="text-muted small">Created</div>
<div class="fw-semibold">@FormatDate(Model.CreatedOn)</div>
</div>
</div>
<div class="col-6">
<div class="border rounded-3 p-3">
<div class="text-muted small">Last Updated</div>
<div class="fw-semibold">@FormatDate(Model.ModifiedOn)</div>
</div>
</div>
</div>
<div class="mt-3 border rounded-3 p-3 bg-light">
<div class="text-muted small mb-1">Users Assigned</div>
<div class="h5 mb-0">@totalUsers</div>
</div>
<div class="mt-3 border rounded-3 p-3 bg-light">
<div class="text-muted small mb-1">Claims Assigned</div>
<div class="h5 mb-0">@totalClaims</div>
</div>
</div>
</div>
</div>
<!-- RIGHT: Users in Role + Role Claims (stacked) -->
<div class="col-lg-7 d-flex flex-column gap-1">
<!-- Role Claims (directly under Users list on the right) -->
<div class="card border-0 shadow">
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center">
<span class="fw-semibold">Role Claims</span>
<a asp-action="ManageClaims" asp-route-id="@Model.Id" class="btn btn-sm btn-primary">
<i class="bi bi-key me-1"></i> Manage Claims
</a>
</div>
<div class="card-body">
@if (Model.Claims == null || Model.Claims.Count == 0)
{
<div class="text-center text-muted py-3">
<i class="bi bi-key mb-2" style="font-size:1.4rem;"></i>
<div>No claims assigned to this role</div>
<div class="mt-2">
<a asp-action="ManageClaims" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-primary">Assign Claims</a>
</div>
</div>
}
else
{
<div class="row row-cols-3 g-2">
@foreach (var claim in Model.Claims.OrderBy(x => x))
{
<div class="col">
<span class="badge rounded-pill bg-primary w-100 text-start px-3 py-2 fs-7 d-inline-flex align-items-center">
<i class="bi bi-check2-circle me-2"></i>@claim
</span>
</div>
}
</div>
}
</div>
</div>
<!-- Users in Role -->
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-2">
<h5 class="card-title mb-0">Users in this Role</h5>
@if (totalUsers > 0)
{
<div class="text-muted small">Showing @start–@end of @totalUsers users</div>
}
</div>
@if (Model.Users.Items.Any())
{
<div class="table-responsive">
<table class="table table-sm table-hover align-middle mb-0">
<thead class="table-dark">
<tr>
<th style="width: 30%;">Name</th>
<th style="width: 34%;">Email</th>
<th style="width: 20%;">Phone</th>
<th style="width: 16%;">Status</th>
</tr>
</thead>
<tbody>
@foreach (var u in Model.Users.Items)
{
var fullName = $"{u.FirstName} {u.LastName}".Trim();
<tr>
<td>@(!string.IsNullOrWhiteSpace(fullName) ? fullName : "—")</td>
<td><span title="@u.Email">@u.Email</span></td>
<td>@(string.IsNullOrWhiteSpace(u.PhoneNumber) ? "—" : u.PhoneNumber)</td>
<td>
@if (u.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-secondary">Inactive</span>
}
</td>
</tr>
}
</tbody>
</table>
</div>
<partial name="~/Views/Shared/_Pager.cshtml"
model="new PagedResult<object> { Items = Array.Empty<object>(), TotalCount = Model.Users.TotalCount, PageNumber = Model.Users.PageNumber, PageSize = Model.Users.PageSize }" />
}
else
{
<div class="border rounded-3 p-4 text-center bg-light mt-2">
<div class="h5 mb-1">No users assigned to this role yet</div>
<div class="text-muted">Add users to <strong>@Model.Name</strong> from your user management page.</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
Create a new ManageClaims View

Create a view named ManageClaims.cshtml within the Views/Roles folder, then copy and paste the following code. This page lists assignable claims as checkboxes, shows total/selected counters, supports Select All / Clear All, and posts back to save the role’s claims.

@model ASPNETCoreIdentityDemo.ViewModels.Roles.RoleClaimsEditViewModel
@{
ViewData["Title"] = "Manage Role Claims";
}
<div class="container mt-2">
<div class="mx-auto" style="max-width:1100px;">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3 pb-2 border-bottom">
<div>
<h1 class="h4 fw-bold text-primary mb-1">
Manage Claims for <span class="text-dark">@Model.RoleName</span>
</h1>
<p class="text-muted small mb-0">
Only <strong>Role</strong> and <strong>Both</strong> category claims are shown.
</p>
</div>
<!-- Right side buttons -->
<div class="d-flex gap-2">
<a asp-action="Index" class="btn btn-primary">
<i class="bi bi-arrow-left me-1"></i> Back to Roles
</a>
<a asp-action="Details" asp-route-id="@Model.RoleId" class="btn btn-success">
<i class="bi bi-arrow-left me-1"></i> Back to Details
</a>
</div>
</div>
<div class="card shadow border-0 rounded">
<div class="card-header bg-dark text-white d-flex align-items-center justify-content-between flex-wrap gap-2">
<div class="d-flex align-items-center gap-4">
<span class="fw-semibold fs-5"><i class="bi bi-key me-2"></i>Claim Assignments</span>
<div class="d-flex gap-3 align-items-center">
<span class="badge bg-info text-dark fs-6 px-3 py-2 rounded-pill">
<i class="bi bi-list-check me-1"></i>Total: @Model.Claims.Count
</span>
<span class="badge bg-success fs-6 px-3 py-2 rounded-pill" id="selectedCount">
<i class="bi bi-check-circle-fill me-1"></i>Selected: @(Model.Claims.Count(c => c.IsSelected))
</span>
</div>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-success btn-sm px-3" id="selectAllBtn">
<i class="bi bi-check2-square me-1"></i>Select All
</button>
<button type="button" class="btn btn-warning btn-sm px-3 text-dark" id="clearAllBtn">
<i class="bi bi-x-circle me-1"></i>Clear All
</button>
</div>
</div>
<div class="card-body">
<form asp-action="ManageClaims" method="post" id="claimsForm">
<input type="hidden" asp-for="RoleId" />
<input type="hidden" asp-for="RoleName" />
@if (!ViewData.ModelState.IsValid && ViewData.ModelState.Values.Any(v => v.Errors.Count > 0))
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<div class="fw-semibold mb-1">We couldn’t save your changes.</div>
<div class="small text-muted mb-2">Please review the messages below and try again.</div>
<div asp-validation-summary="All"></div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
<div class="row" id="claimsList">
@for (int i = 0; i < Model.Claims.Count; i++)
{
var cid = $"claim_{i}";
var type = Model.Claims[i].ClaimType ?? "";
var value = Model.Claims[i].ClaimValue ?? "";
var desc = Model.Claims[i].Description ?? "";
<div class="col-md-6 col-lg-6 mb-3">
<label class="list-group-item rounded shadow-sm border">
<div class="d-flex align-items-start">
<input asp-for="Claims[i].IsSelected" class="form-check-input me-2 mt-1" id="@cid" />
<div class="flex-grow-1">
<div class="fw-semibold">@type : @value</div>
<div class="text-muted small">
@(string.IsNullOrWhiteSpace(desc) ? "No description available" : desc)
</div>
</div>
</div>
<input asp-for="Claims[i].ClaimId" type="hidden" />
<input asp-for="Claims[i].ClaimType" type="hidden" />
<input asp-for="Claims[i].ClaimValue" type="hidden" />
<input asp-for="Claims[i].Description" type="hidden" />
<input asp-for="Claims[i].Category" type="hidden" />
</label>
</div>
}
</div>
<div class="d-flex justify-content-end gap-2 mt-1">
<button type="submit" class="btn btn-primary px-4">
<i class="bi bi-save me-1"></i> Save Claims
</button>
<a asp-action="Details" asp-route-id="@Model.RoleId" class="btn btn-info px-4">Cancel</a>
</div>
</form>
</div>
<div class="card-footer bg-light small text-muted">
Tip: “Select all” and “Clear all” apply to every claim on this page.
</div>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script>
(function ($) {
const $list = $('#claimsList');
const $selectedCount = $('#selectedCount');
function updateSelectedCount() {
const count = $list.find('input[type="checkbox"]:checked').length;
$selectedCount.text('Selected: ' + count);
}
$('#selectAllBtn').on('click', function () {
$list.find('input[type="checkbox"]').prop('checked', true);
updateSelectedCount();
});
$('#clearAllBtn').on('click', function () {
$list.find('input[type="checkbox"]').prop('checked', false);
updateSelectedCount();
});
$list.on('change', 'input[type="checkbox"]', updateSelectedCount);
updateSelectedCount();
})(jQuery);
</script>
}

Now, run the application and test its functionalities; it should work as expected. The role-specific claims will be stored in the RoleClaims table.

Role Claims in ASP.NET Core Identity

Managing role claims in ASP.NET Core Identity helps apply consistent authorization rules across all users within a role. Instead of assigning claims to each user individually, administrators can assign them at the role level, which is more scalable and maintainable. With the implemented UI, services, and controller actions, the application now provides a complete and professional way to handle Role Claims Management alongside user claims.

In the next article, I will discuss Claims-Based Authorization in ASP.NET Core IdentityIn this article, I explain how to add or Remove Role Claims in ASP.NET Core Identity. I hope you enjoy this article, Manage Role Claims in ASP.NET Core Identity.

1 thought on “Manage Role Claims in ASP.NET Core Identity”

  1. blank

    Watch the Video Tutorial
    Want to see this concept in action? Check out my step-by-step YouTube tutorial on Managing Role Claims in ASP.NET Core Identity. In the video, I walk you through the complete implementation with controllers, services, and views so you can easily follow along.

    👉 Watch the full video here: https://youtu.be/UddqvKzxAk4

Leave a Reply

Your email address will not be published. Required fields are marked *