Saturday, September 7, 2019

Manage user claims in asp.net core

We will discuss, what a claim is in detail in a later video. For now you can think of a claim as a name value pair which can be used for making access control decisions. For example, we want to allow a logged-in user to be able to edit employee details, only if he has "Edit Employee" claim. We can use claims to make authorization checks like this. Since we are using claims to make authorization checks, this is called claims-based authorization. 


On EditUser view, when Manage Claims button is clicked, we want to redirect to ManageUserClaims action passing it the UserId

asp net core identity add claim to user

<div class="card-footer">
    <a asp-action="ManageUserClaims" asp-route-userId="@Model.Id"
        style="width:auto" class="btn btn-primary">
        Manage Claims
    </a>
</div>

ManageUserClaims view is as shown below. This view displays the list of all claims in our application. If you want to add a claim to the user, check the checkbox, otherwise leave it unchecked.

asp.net core add or remove user claims

User claims are stored in AspNetUserClaims table. So, once the Update button is clicked the data should be updated in the underlying AspNetUserClaims database table.

asp.net core aspnetuserclaims table

Claims Store

To keep it simple, for our sample application, we are storing claims in the following ClaimsStore static class. You could store them in a configuration file, database table etc.

public static class ClaimsStore
{
    public static List<Claim> AllClaims = new List<Claim>()
    {
        new Claim("Create Role""Create Role"),
        new Claim("Edit Role","Edit Role"),
        new Claim("Delete Role","Delete Role")
    };
}

Claim class is in System.Security.Claims namespace. The constructor expects - Claim type and value.

UserClaimsViewModel

public class UserClaimsViewModel
{
    public UserClaimsViewModel()
    {
        Cliams = new List<UserClaim>();
    }

    public string UserId { getset; }
    public List<UserClaim> Cliams { getset; }
}
  • This viewmodel class carries the data from the controller action to the view and vice versa
  • As far as this view is concerned, there is a one-to-many relationship from the user to claim.
  • As the name implies UserId property holds the ID of the user for whom we are adding or removing a claim
  • Claims property which is of type List<UserClaim> holds the list of claims
  • The following is the UserClaim class. IsSelected property determines if the clam is selected on the UI
public class UserClaim
{
    public string ClaimType { get; set; }
    public bool IsSelected { get; set; }
}

HttpGet ManageUserClaims Action

[HttpGet]
public async Task<IActionResult> ManageUserClaims(string userId)
{
    var user = await userManager.FindByIdAsync(userId);

    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {userId} cannot be found";
        return View("NotFound");
    }

    // UserManager service GetClaimsAsync method gets all the current claims of the user
    var existingUserClaims = await userManager.GetClaimsAsync(user);

    var model = new UserClaimsViewModel
    {
        UserId = userId
    };

    // Loop through each claim we have in our application
    foreach (Claim claim in ClaimsStore.AllClaims)
    {
        UserClaim userClaim = new UserClaim
        {
            ClaimType = claim.Type
        };

        // If the user has the claim, set IsSelected property to true, so the checkbox
        // next to the claim is checked on the UI
        if (existingUserClaims.Any(c => c.Type == claim.Type))
        {
            userClaim.IsSelected = true;
        }

        model.Cliams.Add(userClaim);
    }

    return View(model);

}

HttpPost ManageUserClaims Action

[HttpPost]
public async Task<IActionResult> ManageUserClaims(UserClaimsViewModel model)
{
    var user = await userManager.FindByIdAsync(model.UserId);

    if (user == null)
    {
        ViewBag.ErrorMessage = $"User with Id = {model.UserId} cannot be found";
        return View("NotFound");
    }

    // Get all the user existing claims and delete them
    var claims = await userManager.GetClaimsAsync(user);
    var result = await userManager.RemoveClaimsAsync(user, claims);

    if (!result.Succeeded)
    {
        ModelState.AddModelError("""Cannot remove user existing claims");
        return View(model);
    }

    // Add all the claims that are selected on the UI
    result = await userManager.AddClaimsAsync(user,
        model.Cliams.Where(c => c.IsSelected).Select(c => new Claim(c.ClaimType, c.ClaimType)));

    if (!result.Succeeded)
    {
        ModelState.AddModelError("""Cannot add selected claims to user");
        return View(model);
    }

    return RedirectToAction("EditUser"new { Id = model.UserId });

}

ManageUserClaims View

@model UserClaimsViewModel

<form method="post">
    <div class="card">
        <div class="card-header">
            <h2>Manage User Claims</h2>
        </div>
        <div class="card-body">
            @for (int i = 0; i < Model.Cliams.Count; i++)
            {
                <div class="form-check m-1">
                    <input type="hidden" asp-for="@Model.Cliams[i].ClaimType" />
                    <input asp-for="@Model.Cliams[i].IsSelected" class="form-check-input" />
                    <label class="form-check-label" asp-for="@Model.Cliams[i].IsSelected">
                        @Model.Cliams[i].ClaimType
                    </label>
                </div>
            }
            <div asp-validation-summary="All" class="text-danger"></div>
        </div>
        <div class="card-footer">
            <input type="submit" value="Update" class="btn btn-primary"
                   style="width:auto" />
            <a asp-action="EditUser" asp-route-id="@Model.UserId"
               class="btn btn-primary" style="width:auto">Cancel</a>
        </div>
    </div>
</form>

No comments:

Post a Comment

How to register multiple implementations of the same interface in Asp.Net Core?

 Problem: I have services that are derived from the same interface. public interface IService { } public class ServiceA : IService { ...