ASP.NET Core 3.1 - Admin Role


Ken Haggerty
Created 02/01/2020 - Updated 04/24/2020 15:07

This article will demonstrate the implementation of an admin role to manage users. I will assume you have created a new ASP.NET Core 3.1 Razor Pages project and have implemented the first 4 articles of the series. See Tutorial: Get started with Razor Pages in ASP.NET Core .

Users Without Identity Project and Article Series


Access to the research project source code may be purchased on KenHaggerty.Com at Manage > Assets.

A project which implements users without Identity has been published to demo.kenhaggerty.com.

I enjoy writing these articles. It often enhances and clarifies my coding. The research project is a result of a lot of refactoring and hopefully provides logical segues for the articles. Thank you for supporting my efforts.

I don't want all users to have access to create, read, update and delete other users. I implemented Authorization with a Role claim, an IsAdmin property for the user and an Admins policy. Add the IsAdmin bool property to the AppUser class.

Edit Entities > AppUser.cs:
public class AppUser
{
    [Key]
    public int Id { get; set; }

    [Required]
    [Display(Name = "Login Name")]
    [StringLength(32, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
    public string LoginName { get; set; }

    [Required]
    [StringLength(32)]
    public string LoginNameUppercase { get; set; }

    [Required]
    [MaxLength(84, ErrorMessage = "The {0} is max {1} characters long.")]
    public string PasswordHash { get; set; }

    [Display(Name = "Admin")]
    public bool IsAdmin { get; set; }
}

Add the AdminRole migration.

Package Manager Console:
add-migration AdminRole -o Data/Migrations
DotNet CLI:
dotnet-ef migrations add AdminRole -o Data/Migrations

The add migration produces a MigrationBuilder which will add the IsAdmin column to the AppUsers table. The file name prepends a date time stamp to AdminRole.

20200120183102_AdminRole.cs:
public partial class AdminRole : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn<bool>(
            name: "IsAdmin",
            table: "AppUsers",
            nullable: false,
            defaultValue: false);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(
            name: "IsAdmin",
            table: "AppUsers");
    }
}

If all looks good, update the database.

Package Manager Console:
update-database
DotNet CLI:
dotnet-ef database update

Add the IsAdmin property to the InputModels for the create and edit user pages.

Edit Admin/ Users/ Create.cshtml.cs && Edit.cshtml.cs > InputModel:
[Display(Name = "Admin")]
public bool IsAdmin { get; set; }

Update the pages with IsAdmin inputs.

Edit Create.cshtml && Edit.cshtml:
<div class="form-group">
    <div class="custom-control custom-checkbox">
        <input class="custom-control-input" asp-for="Input.IsAdmin" />
        <label class="custom-control-label" asp-for="Input.IsAdmin">
            @Html.DisplayNameFor(model => model.Input.IsAdmin)
        </label>
    </div>
    <span asp-validation-for="Input.IsAdmin" class="text-danger"></span>
</div>

Add/Update the IsAdmin property when you save the user. Be sure to add IsAdmin to the new InputModel in OnGet for the edit page. The code for details and delete doesn't need modification but I add the IsAdmin property to the pages.

If the user's IsAdmin = true, add a Role claim to the Claims Principle when the user logs in.

Edit Account/ Login.cshtml.cs > OnPost:
if (user.IsAdmin)
{
    claims.Add(new Claim(ClaimTypes.Role, "Admin"));
}

Update Services/ EnsureAdministrator.cs. Set IsAdmin = true for the new AppUser we automatically create at startup to guarantee an Administrator user.

Because we placed all the user CRUD pages in the Admin/ Users folder, we can use a policy to restrict access to the entire folder.

Edit Startup.cs > ConfigureServices:
services.AddAuthorization(options =>
{
    options.AddPolicy("Admins", policy =>
    {
        policy.RequireRole("Admin");
    });
});

services.AddRazorPages(options =>
{
    options.Conventions.AuthorizeFolder("/Admin");
    options.Conventions.AuthorizeFolder("/Admin/Users", "Admins");
});

The Admin/ Index page has a Users button which we can hide from non admin users.

Edit Admin/ Index.cshtml:
@if (User.Identity.IsAuthenticated && User.IsInRole("Admin"))
{
    <a class="btn btn-primary" asp-page="/Admin/Users/Index">Users</a>
}

I add the IsAdmin property to the Admin/ Users/ Index page.

Edit Admin/ Users/ Index.cshtml:
<td>
    <div class="custom-control custom-checkbox custom-control-inline">
        <input type="checkbox" class="custom-control-input disabled" asp-for="@item.IsAdmin">
        <label class="custom-control-label disabled">
            <span class="sr-only">Never</span>
        </label>
    </div>
</td>

You can add a NameIdentifier claim to the Claims Principle at login to prevent the current user from deleting themself.

Edit Account/ Login.cshtml.cs > OnPost:
var claims = new List<Claim>
{
    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.Integer),
    new Claim(ClaimTypes.Name, user.LoginName)
};
Edit Admin/ Users /Index.cshtml.cs:
public int CurrentUserId { get; set; }

public async Task OnGetAsync()
{
    var idClaim = User.FindFirst(ClaimTypes.NameIdentifier);            
    if (idClaim != null)
    {
        try
        {
            CurrentUserId = Int32.Parse(idClaim.Value);
        }
        catch (FormatException e)
        {
            Console.WriteLine(e.Message);
        }
    }
    AppUsers = await _context.AppUsers.ToListAsync();
}
Edit Admin/ Users /Index.cshtml:
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a>
@if (Model.CurrentUserId != item.Id)
{

    <text> | </text>
    <a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
}

Build, run and test. You will have to reset the Administrator user in Services/ EnsureAdministrator.cs.

Users Index page
Update 04/24/2020

I published the User Database Service article and updated the article links. I implemented a UserService in the Users Without Identity Project at v1.0.6 and have updated the project's NuGet packages, details and download.



Comment Count = 0

Please log in to comment or follow.

Login Register
Follow to get web notifications when new comments are posted to this article.
Logged in users receive web notifications for new articles, topics and assets.
Web Notifications