Custom User Management with NET8 and Blazor

blazor microsoft identity

In this new series of posts, I will show how to create custom user management with NET8 and Blazor based on Microsoft Identity.

In every application I wrote, there is always the requirement to authenticate the user: in this new series of posts, I will show how to create custom user management with NET8 and Blazor based on Microsoft Identity.

The full source code of this post is available on GitHub. If you find this post useful, please consider making a donation on GitHub.

For any comment, suggestion or help, please see the Forum.

Other posts:

Microsoft Identity: overview

Every time we build an application, the first point of concern is how to manage the users and their roles and the security across the application. The basic essence of the requirement is always the same, which is to register, login, authorize users, roles, and so on. So, to help ease the user management process, Microsoft comes up with a default implementation of User Management. The name is Microsoft Identity, Also, it has built-in UI to support various user functionalities. Developers who are looking for a faster way to implement User Management, tend to go with Identity. You can learn more about Identity here.

Now, out of the box, Identity comes with certain basic features. In real scenarios, we may need much more than what Microsoft offers by default. This includes adding Profile Pictures, UI for Role Management, Custom logic to log in to the user, and much more.

Setting up the Blazor Application

At the time I’m writing with post, the only Visual Studio available is the version 2022 Preview and you can download it from here. This is the only way to use NET8. So, first Create a new project. From the list, choose Blazor Web App.

Create a new project - Custom User Management with NET8 and Blazor
Create a new project

Give it a name and a location.

Configure your new project - Custom User Management with NET8 and Blazor
Configure your new project

In the Addition information step, from the dropdown list Authentication Type, select Individual Accounts.

Additional Information - Custom User Management with NET8 and Blazor
Additional Information

This is the result of the solution created by Visual Studio. How you can see, in the server project, under the Pages folder there is an Account folder with all the pages for the Identity.

Blazor Identity solution - Custom User Management with NET8 and Blazor
Blazor Identity Solution

Renaming the default Identity tables and updating

Before moving on, let’s update the database. As soon as we created our project, Visual Studio has done the following for us already.

  • Added migrations for the Identity Table.
  • Generated a default DB Context
  • Registered the DB Context in the Startup.cs
  • Added a default connection string to appsettings.json (a local DB with the project name and GUID)

Since everything is set up for us, let’s apply the migrations and update the database. Open up the package manager console and type in the following.

update-database

Once that’s done, open up the SQL Server Object Explorer in Visual Studio. You can see our newly generated Identity tables here.

Identity Table
Identity Tables

Now, there is one thing that catches the eyes of many. The Table Names. Quite ugly with the ASPNET Naming convention, right? Let’s change that now.

We will delete our new database for now.

drop-database

Also, delete the migrations folder (found inside the Data Folder), as we are going to generate a new one. Here is a simple solution. Since we are by default using Entity Framework Core, let’s open up the ApplicationDbContext.cs from the Data Folder. To modify the default ugly names of the Identity Tables, add this override function,

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.HasDefaultSchema("Identity");
    builder.Entity<IdentityUser>(entity =>
    {
        entity.ToTable(name: "User");
    });
    builder.Entity<IdentityRole>(entity =>
    {
        entity.ToTable(name: "Role");
    });
    builder.Entity<IdentityUserRole<string>>(entity =>
    {
        entity.ToTable("UserRoles");
    });
    builder.Entity<IdentityUserClaim<string>>(entity =>
    {
        entity.ToTable("UserClaims");
    });
    builder.Entity<IdentityUserLogin<string>>(entity =>
    {
        entity.ToTable("UserLogins");
    });
    builder.Entity<IdentityRoleClaim<string>>(entity =>
    {
        entity.ToTable("RoleClaims");
    });
    builder.Entity<IdentityUserToken<string>>(entity =>
    {
        entity.ToTable("UserTokens");
    });
}

Line #4, sets a schema for the database.
Line #7, renames the User Table from ASPNETUsers to Identity.User. Clean enough? Feel free to add tables names that can make more sense to you, Similarly, we rename all the table entries.

With that out of the way, let’s add the migrations and update the database.

add-migration "Renamed Identity Table Names"
update-database

Now, have a look at the database. What do you think? Is that much clearer now?

Renamed Identity Table Names
Renamed Identity Table Names

Adding Custom Fields to Identity User

If you go through the Identity.User Table, you can find over 10-15 columns that are available by default. What if we wanted to add certain user-specific properties like First Name, Image, and something else?

For that, we have to need to extend the IdentityUser class with your own properties. In the Data folder, use can find an ApplicationUser.cs that inherits from the IdentityUser.

namespace LIU.Website.Data
{
    // Add profile data for application users by adding properties to the ApplicationUser class
    public class ApplicationUser : IdentityUser
    {
    }
}

So, in this class, I’m going to add a few properties to collect the following info about the new user:

  • first name
  • last name
  • number of changes to the username
  • profile picture

The new ApplicationUser is

public class ApplicationUser : IdentityUser
{
	public string? FirstName { get; set; }
	public string? LastName { get; set; }
	public int UsernameChangeLimit { get; set; } = 10;
	public byte[]? ProfilePicture { get; set; }
}

Since we decided to change the default User class from IdentityUser to ApplicationUser, we would have to make other changes in our existing code as well. Now, add another migration

add-migration "Added Custom Properties"
update-database

Now, if you open the table Identity.AspNetUsers we can see the changes to the table.

Custom fields in the AspNetUsers table
Custom fields in the AspNetUsers table

Extending the Registration Form

Now that we have added the extra fields, let’s use them in the registration process. Navigate to Pages/Account/Register.razor. The registration page is this one

Registration page with Blazor NET8
Registration page with Blazor NET8

What we have to do now is to change the model of this page to collect the first name and last name and then change the UI to display those values.

Change InputModel

So, in the code section of the page almost at the beginning, you find the InputModel that has all the default fields. Now, we are going to add the fields FirstName and LastName. The new InputModel is

public class InputModel
{
    /// <summary>
    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
    ///     directly from your code. This API may change or be removed in future releases.
    /// </summary>
    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; } = null!;

    /// <summary>
    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
    ///     directly from your code. This API may change or be removed in future releases.
    /// </summary>
    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; } = null!;

    /// <summary>
    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
    ///     directly from your code. This API may change or be removed in future releases.
    /// </summary>
    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; } = null!;

    [Required]
    [Display(Name = "First Name")]
    public string FirstName { get; set; } = null!;

    [Required]
    [Display(Name = "Last Name")]
    public string LastName { get; set; } = null!;
}

Next, we will need to pass data to these properties and save it to the database while registering. If you scroll down in the page, there is a method RegisterUser that has as a parameter EditContext editContext (here the first lines)

public async Task RegisterUser(EditContext editContext)
{
    var user = CreateUser();

    await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
    var emailStore = GetEmailStore();
    await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
    var result = await UserManager.CreateAsync(user, Input.Password);

    if (result.Succeeded)
    {

So, this method in the line var user = CreateUser(); is calling the function CreateUser. I’m going to delete this line and change it with this code

        MailAddress address = new MailAddress(Input.Email);
        string userName = address.User;

        var user = new ApplicationUser()
            {
                UserName = userName,
                Email = Input.Email,
                FirstName = Input.FirstName,
                LastName = Input.LastName
            };

Remember to add @using System.Net.Mail at the top of the file. The code above creates the Applicationser after generating a username from the email address.  For example, if the email address entered by the user is info@puresourcecode.com, the username generated will be info.

Change the form

Now, I have to display the fields in the registration form. For that, at the beginning of the page and after the EditForm I add the following HTML code

<div class="form-floating mb-3">
    <InputText id="firstname" @bind-Value="Input.FirstName" class="form-control" aria-required="true" />
    <label for="firstname">First name</label>
    <ValidationMessage For="() => Input.FirstName" class="text-danger" />
</div>
<div class="form-floating mb-3">
    <InputText id="lastname" @bind-Value="Input.LastName" class="form-control" aria-required="true" />
    <label for="lastname">First name</label>
    <ValidationMessage For="() => Input.LastName" class="text-danger" />
</div>

Let’s check out the result. Build and run the application. Navigate to the Register Page. You can see the new fields now. Add some details to the form and try to register.

The new registration form with custom fields
The new registration form with custom fields

Now, after the authentication, I can see this page. On the left side, the username (alias my email) is displayed.

Because I added the first name of a user, I want to display the first name if it is available or, instead the name from the Identity. So, open the NavMenu.razor and scroll down in the AuthorizeView. What I can see is

<AuthorizeView>
    <Authorized>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="/Account/Manage">
                <span class="bi bi-person-fill" aria-hidden="true"></span> @context.User.Identity?.Name
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <LogoutForm id="logout-form" />
            <NavLink class="nav-link" href="#" onclick="document.getElementById('logout-form').submit(); return false;">
                <span class="bi bi-arrow-bar-left" aria-hidden="true"></span> Logout
            </NavLink>
        </div>
    </Authorized>

In order to display the first name, I have to change the line 5. First, at the top of the page add

// those 2 lines are related to the project
@using BlazorIdentity.Components.Identity
@using BlazorIdentity.Data

@using Microsoft.AspNetCore.Identity
@inject UserManager<ApplicationUser> usermanager

The first 2 lines are related to my project. So, replace BlazorIdentity with the name of your project. In the last line, I inject the UserManager from the Microsoft.AspNetCore.Identity. Now, with this line I can write the new line

<AuthorizeView>
    <Authorized>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="/Account/Manage">
                <span class="bi bi-person-fill" aria-hidden="true"></span> 
                @(usermanager?.GetUserAsync(context.User)?.Result?.FirstName ?? context.User.Identity?.Name)
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <LogoutForm id="logout-form" />
            <NavLink class="nav-link" href="#" onclick="document.getElementById('logout-form').submit(); return false;">
                <span class="bi bi-arrow-bar-left" aria-hidden="true"></span> Logout
            </NavLink>
        </div>
    </Authorized>

Now, it is better!

Read from the Identity a custom property
Read from the Identity a custom property

By default, in Identity, the username and Email are the same. Now, in the login form, the application expects the username in the email field. But our username is no longer an email ID, remember? Let’s fix this in the next post.

4 thoughts on “Custom User Management with NET8 and Blazor

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.