Application authentication
.NET Core ASP.NET Core ASP.NET Core Identity C# OAuth Razor Visual Studio

How to Extend Identity Accounts to use Claims in .NET Core Applications

Welcome to today’s post.

I will be discussing the creation and maintenance of user claims for user accounts using the ASP.NET Core Identity framework.

In a previous post I showed how to extend the user identity within a ASP.NET Core Identity application.

If you are already familiar with ASP.NET Core Identity and how to scaffold an identity implementation into your own ASP.NET MVC application or implemented custom identity based  security authentication, then this discussion will be an extension on how you can implement claims into your application. 

The motivation behind adding claims into your user accounts is to implement claims-based authorization into your applications, which can be used to implement more secure token-based security, such as JWT or OAuth2.

Before you can use the security claims API, declare the following namespace at the top of your source file:

using System.Security.Claims;

In the first section, I will show how to add claims for new user accounts.

Adding Claims for New Users

The most common function that is used to add new claims is when registering an new user account during user registration.

In the code excerpt below, we are defining a list of claims that are user properties:

var newClaims = new List<Claim>() {
    new Claim(ClaimTypes.GivenName, user.FirstName),
    new Claim(ClaimTypes.Surname, user.LastName),
    new Claim(ClaimTypes.DateOfBirth, user.DOB.ToShortDateString())
};

We then add the list of claims for the user using the ASP.NET Core Identity Core UserManager class

method AddClaimsAsync() as shown:

await _userManager.AddClaimsAsync(user, newClaims);

In the next section, I will cover what prerequisites are required before claims can be added to a user account.

Prerequisites before Adding User Claims

Before claims can be added to a user account, the user needs to exist in the accounts tables in the identity database.

To add a new user account, you need to first create a new user object containing the user of type ApplicationUser:

var user = new ApplicationUser {
    UserName = model.Email,
    Email = model.Email,
    FirstName = model.FirstName,
    LastName = model.LastName,
    DOB = model.DOB
};

Then you create the user account from the object instance by calling the CreateAsync() method of the ASP.NET Core Identity Core UserManager class:

var result = await _userManager.CreateAsync(user, model.Password);

The next requirement is to sign in the user account into the active web session. We do this by calling the SignInAsync() method of the ASP.NET Core Identity Core SignInManager class:

await _signInManager.SignInAsync(user, isPersistent: false);

In the next section, I will show how to use the above code excerpts to implement a basic user registration method.

Adding Claims as Part of a User Registration

Once we have created the user account, we are ready to implement a user register method.

The previous code excerpts that we saw were the key pieces of functionality in a method that registers the details of a user as a new account from within an account controller:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
   ViewData["ReturnUrl"] = returnUrl;
   if (ModelState.IsValid)
   {
      var user = new ApplicationUser {
         UserName = model.Email,
         Email = model.Email,
         FirstName = model.FirstName,
         LastName = model.LastName,
         DOB = model.DOB
      };
      var result = await _userManager.CreateAsync(user,
         model.Password);
      if (result.Succeeded)
      {
         _logger.LogInformation(
            "User created a new account with password.");
         var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
         var callbackUrl = Url.EmailConfirmationLink(
            user.Id, code, Request.Scheme);
         await _emailSender.SendEmailConfirmationAsync(               
            model.Email, callbackUrl);

         await _signInManager.SignInAsync(user, 
            isPersistent: false);
         _logger.LogInformation("User created a new account with password.");
         // add additional claims for this new user..
         var newClaims = new List<Claim>() {
            new Claim(ClaimTypes.GivenName, user.FirstName),
            new Claim(ClaimTypes.Surname, user.LastName),
            new Claim(ClaimTypes.DateOfBirth,
            user.DOB.ToShortDateString())
         };
         await _userManager.AddClaimsAsync(user, newClaims);
         _logger.LogInformation("Claims added for user.");

         return RedirectToLocal(returnUrl);
      }
      AddErrors(result);
   }

   // If execution got this far, something failed, redisplay the form.
   return View(model);
}

The above method could be run as part of a call to a Web API service, or within a user security administration application. When the method is executed with valid user properties, a new user account will have been created within the identity database. To check the user has been added, we check the [AspNetUserClaims] table and the claims data for the user:

In the next section, I will show how to update user claims within an ASP.NET Core application.

Updating User Claims within an ASP.NET Core Application

When user account details are updated, user account properties that are also claims can also be updated, as shown in the code below:

First, we retrieve the user account:

var user = await _userManager.GetUserAsync(User);

We then retrieve the claims for the user, then retrieve the first name of the user:

bool bHasFNClaim = false;
dynamic existingFNClaim = "";

var userClaims = await _userManager.GetClaimsAsync(user);
foreach (Claim claim in userClaims)
{
    if (claim.Type == ClaimTypes.GivenName)
    {
       	bHasFNClaim = true;
        existingFNClaim = (Claim)claim;
        …
    }
}

To determine if the user account has a claim that corresponds to the first name, we have set a flag bHasFNClaim. The claim, if retrieved is also assigned to the dynamic variable existingFNClaim.

As I will show soon, we will need a new claim instance for the given name. It will be used to help us add or update a claim:

var newFNClaim = new Claim(
    ClaimTypes.GivenName,
    user.FirstName
);

If there is no claim for a first name claim in the user account’s claims, then we add the instance of the first name object to the claims for the user account using the AddClaimAsync() method of the ASP.NET Identity Core UserManager class. If the first name claim exists in the user account’s claims, then we need to call both RemoveClaimAsync() and AddClaimAsync() methods of the ASP.NET Identity Core UserManager class as shown:

if (!bHasFNClaim)
{
    await _userManager.AddClaimAsync(user, newFNClaim);
}
else if (bHasFNUpdated)
{
    if (existingFNClaim.GetType().ToString() == "System.Security.Claims.Claim")
    {
        await _userManager.RemoveClaimAsync(user, existingFNClaim);
        await _userManager.AddClaimAsync(user, newFNClaim);
    }
}

The removal of the existing claim instance, and addition of the new claim instance is effectively updating the user claim.

We then commit the claim changes with the following call:

await _userManager.UpdateAsync(user);

In the next section, I will show how to update user claims for the most important properties of a user account.

Updating Claims for a User Account

In the code excerpt that follows, I will check the existence of all the user claims, then either add or update the corresponding claim in the claims within the current user account.

var user = await _userManager.GetUserAsync(User);
if (user == null)
{
   throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}

var email = user.Email;
if (model.Email != email)
{
   var setEmailResult = await _userManager.SetEmailAsync(
      user, model.Email);
   if (!setEmailResult.Succeeded)
   {
      throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
   }
}

var phoneNumber = user.PhoneNumber;
if (model.PhoneNumber != phoneNumber)
{
   var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);
   if (!setPhoneResult.Succeeded)
   {
      throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
   }
}

Next, we verify which of our claims properties has been updated:

// Flags for updated claims fields.
bool bHasFNUpdated = false;
bool bHasSNUpdated = false;
bool bHasDOBUpdated = false;

// custom field updates
if (model.FirstName != user.FirstName)
{
   user.FirstName = model.FirstName;
   bHasFNUpdated = true;
}

if (model.LastName != user.LastName)
{
   user.LastName = model.LastName;
   bHasSNUpdated = true;
}

if (model.DOB != user.DOB)
{
   user.DOB = model.DOB;
   bHasDOBUpdated = true;
}

Next, we determine which claims are existing in the user’s claims, and then assign these claims to a Claim object:

// add/update claims 
bool bHasFNClaim = false;
bool bHasSNClaim = false;
bool bHasDOBClaim = false;

dynamic existingFNClaim = "";
dynamic existingSNClaim = "";
dynamic existingDOBClaim = "";

// check existing claims for user..
var userClaims = await _userManager.GetClaimsAsync(user);
foreach (Claim claim in userClaims)
{
   if (claim.Type == ClaimTypes.GivenName)
   {
      bHasFNClaim = true;
      existingFNClaim = (Claim)claim;
   }
   if (claim.Type == ClaimTypes.Surname)
   {
      bHasSNClaim = true;
      existingSNClaim = (Claim)claim;
   }
   if (claim.Type == ClaimTypes.DateOfBirth)
   {
      bHasDOBClaim = true;
      existingDOBClaim = (Claim)claim;
   }
}

Next, we update the claims for the user to the data store. Where a claim already exists in our claims record, the current one is removed before the new claim is added:

var newFNClaim = new Claim(
   ClaimTypes.GivenName,
   user.FirstName
);
var newSNClaim = new Claim(
   ClaimTypes.Surname,
   user.LastName
);
var newDOBClaim = new Claim(
   ClaimTypes.DateOfBirth,
   user.DOB.ToShortDateString()
);

// add/update claims for user..
if (!bHasFNClaim)
{
   await _userManager.AddClaimAsync(user, newFNClaim);
}
else if (bHasFNUpdated)
{
   if (existingFNClaim.GetType().ToString() ==
      "System.Security.Claims.Claim")
   {
      await _userManager.RemoveClaimAsync(user, existingFNClaim);
      await _userManager.AddClaimAsync(user, newFNClaim);
   }
}
if (!bHasSNClaim)
{
   await _userManager.AddClaimAsync(user, newSNClaim);
}
else if (bHasSNUpdated)
{
   if (existingSNClaim.GetType().ToString() ==
      "System.Security.Claims.Claim")
   {
      await _userManager.RemoveClaimAsync(user, existingSNClaim);
      await _userManager.AddClaimAsync(user, newSNClaim);
   }
}
if (!bHasDOBClaim)
{
   await _userManager.AddClaimAsync(user, newDOBClaim);
}
else if (bHasDOBUpdated)
{
   if (existingDOBClaim.GetType().ToString() ==
      "System.Security.Claims.Claim")
   {
      await _userManager.RemoveClaimAsync(user, existingDOBClaim);
      await _userManager.AddClaimAsync(user, newDOBClaim);
   }
}
_logger.LogInformation("Claims added/updated for user.");
await _userManager.UpdateAsync(user);

StatusMessage = "Your profile has been updated";

Essentially what we are doing in the updating of claims is:

  1. Check existing claims for the user.
  2. If the modified user properties that are claims are updated, then remove their respective claims (from 1) and add new claims with the modified properties.
  3. If there are no existing claims for the modified user properties, then add new claims with the modified properties.

I have shown how to make use of user claims in the management of user accounts within an ASP.NET Core application. I will show in future posts how to retrieve user claims following authentication within an ASP.NET Core client application. I also will show you how to apply user claims to authorize access within applications.

That’s all for this post.

I hope you have found this post useful and learned something useful.

Social media & sharing icons powered by UltimatelySocial