How to add extra user claims in ASP.NET Core web-app

korzh

Sergiy Korzh

Posted on July 26, 2019

How to add extra user claims in ASP.NET Core web-app

Introduction

ASP.NET Core has sufficiently developed tools for implementing authentication and authorization mechanisms in your projects. These tools are available out-of-the-box and rather simple in configuring. Even the starting web-project created with Visual Studio wizard already includes all the necessary parts of the authorization subsystem (Registration/Login/User Profile forms, Reset password procedure, etc.)

However, some at first glance, simple tasks require a deeper understanding of the internal structure of ASP.NET Core Identity.

In this article, we will discuss one of these tasks and suggest a solution to it using the claims-based approach.

Problem

Let's suppose we created a new ASP.NET Core project using one of the default templates and chose "Individual user account" option for "Authentication".

ASP.NET Identity new project

Now when we start that newly created project and register new user we will see something like Hello YourEmailAddress@YourCompany.com in the top right part of the index web-page.

Obviously, such kind of greeting is useless in a real-world application and you would like to see the name of the currently logged user there instead (e.g. Hello John Doe).

Let's figure out how to do it.

Solution

Here we guess you are already familiar with the claims and claims-based approach for authorization used in ASP.NET Core Identity. If not - please read ASP.NET Core Security article first.

To achieve our goal we need to do 2 things:

  1. Add necessary information to the list of the claims attached to the user's identity.
  2. Have a simple way of getting that info when needed.

But before implementing these two tasks we will need to add a new ContactName field to our model class and update our registration and user management pages accordingly.

0. Preparations

Before we can add a new claim to a user object (the one you can access via HttpContext.User) we need a place to store that additional info somewhere.
Here I am going to describe how to get this done for a new ASP.NET Core project built by a default template.

If you work on some real-world project - most probably you already did similar changes before.
In this case, you can skip this "preparations" section and move right to the next one.

1. New ApplicationUser class

Add a new ApplicationUser class with `ContactName' property:

public class ApplicationUser : IdentityUser
{
    public string ContactName { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Of course, you can add more properties to store some additional information with the user account.
For example: FirstName, LastName, Country, Address, etc. All of them can be placed to claims the same way as ContactName we discuss here.

2. Replace IdentityUser with ApplicationUser

Now you need to replace IdentityUser with ApplicationUser everywhere in your project.

The default ASP.NET Core template uses predefined IdentityUser type everywhere.
Since we what to use ApplicationUser instead of it - we need to search for all inclusions of IdentityUser in your project and replace with ApplicationUser.

It will include your DbContext class, one line in Startup class (in ConfigureServices method) and two lines with @inject directives in _LoginPartial.cshtml view.

Here is how your new ApplicationDbContext class will look like after that:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole, string>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Update your database.

Now you need to add a new migration and then update your DB.
Just run the following 2 commands from your project's folder:

dotnet ef migrations add AddUserContactName

dotnet ef database update
Enter fullscreen mode Exit fullscreen mode

4. Update "User Profile" page

Finally, we will need to add our new field to the "User Profile" page to make it possible for users to modify it.

The default ASP.NET Core template uses all identity-related pages directly from a special Razor UI library (Microsoft.AspNetCore.Identity.UI).
The good news is: we can override any of those pages if we want. Here are the steps we need to do:

  1. Right-click on your project in VS and select Add | New Scaffolding item.

  2. In the "Add Scaffold" dialog select Identity on the left side tree and then Identity in the main list and click "Add".

  3. In the dialog that appears select only Account\Manage\Index page and then click on "Add" as well.
    When the process is finished you will find a new page 'Index.cshtml' in Areas/Identity/Pages folder.

  4. After that make the following changes to that Index page:

In the Index.cshtml itself add the following piece of markup right before update-profile-button button.

<div class="form-group">
  <label asp-for="Input.ContactName"></label>
  <input asp-for="Input.ContactName" class="form-control" />
  <span asp-validation-for="Input.ContactName" class="text-danger"></span>
</div>
Enter fullscreen mode Exit fullscreen mode

Then, in the code-behind file Index.cshtml.cs we need to modify the view model:

public class InputModel
{
    .   .   .   .   .   .

    public string ContactName { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

then the OnGetAsync method:

public async Task<IActionResult> OnGetAsync()
{
    .   .   .   .   .   .

    Input = new InputModel
    {
        Email = email,
        PhoneNumber = phoneNumber,
        ContactName = user.ContactName //add this line
    };

    .   .   .   .   .   .

}
Enter fullscreen mode Exit fullscreen mode

and the OnPutAsync:

public async Task<IActionResult> OnPostAsync()
{
    .    .    .    .    .    .    .

    if (Input.ContactName != user.ContactName) {
        user.ContactName = Input.ContactName;
        await _userManager.UpdateAsync(user);
    }

    await _signInManager.RefreshSignInAsync(user);
    StatusMessage = "Your profile has been updated";
    return RedirectToPage();
}
Enter fullscreen mode Exit fullscreen mode

So, after all the changes described above your User Profile page after that registration will look like this:

User Profile form with ContactName field

Now, all the preparations are finished we can return back to our main task.

1. Adding the contact name to the claims

A funny thing: the main task is much easier than all the preparations we made before. :)
Moreover, it became even easier because of some changes in version 2.2 of ASP.NET Core (in comparison with version 2.0 as we described before )

There are only two simple steps:

Create own claims principal factory

We need an implementation IUserClaimsPrincipalFactory which will add necessary information (ContactName in our case) to the user claims.
The simplest way to do it - is to derive our new class from the default implementation of IUserClaimsPrincipalFactory and override one method: GenerateClaimsAsync:

public class MyUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
    public MyUserClaimsPrincipalFactory(
        UserManager<ApplicationUser> userManager,
        IOptions<IdentityOptions> optionsAccessor)
        : base(userManager, optionsAccessor)
    {
    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        identity.AddClaim(new Claim("ContactName", user.ContactName ?? "[Click to edit profile]"));
        return identity;
    }
}
Enter fullscreen mode Exit fullscreen mode

Register new class in DI container

Then we need to register our new class in the dependency injection container.
The best way for that - to use AddClaimsPrincipalFactory extension method:

public void ConfigureServices(IServiceCollection services) 
{
    .     .     .     .      .
    services.AddDefaultIdentity<ApplicationUser>()
        .AddDefaultUI(UIFramework.Bootstrap4)
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddClaimsPrincipalFactory<MyUserClaimsPrincipalFactory>();  //<---- add this line
}
Enter fullscreen mode Exit fullscreen mode

2. Accessing new claim from the views

Now we have a new claim associated with our user's identity. That's fine. But how we can get it and render on our view(s)?
Easy. Any view in your application has access to User object which is an instance of ClaimsPrincipal class.

This object actually holds the list of all claims associated with the current user and you can call its FindFirst method to get the necessary claim and then read the Value property of that claim.

So, we just need to open _LoginPartical.cshtml file in Pages/Shared/ (or Views/Shared/) folder and replace the following line:

<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @User.Identity.Name!</a>
Enter fullscreen mode Exit fullscreen mode

with this one:

<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @(User.FindFirst("ContactName").Value)!</a>
Enter fullscreen mode Exit fullscreen mode

Now you instead of something like Hello john.doe@yourcompany.com at the top of your web-page you should see something like this:

ASP.NET Identity contact name

That's all for now. Enjoy!

The article was originally posted on Korzh.com blog.

💖 💪 🙅 🚩
korzh
Sergiy Korzh

Posted on July 26, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related