Search code examples
identityserver4blazor-client-side

How to attach claims in the token when logging in to a hosted Blazor Wasm app?


I've created a hosted Blazor Wasm with identity. I've create a user. I also added a couple of claims.

var adminClaims = new List<Claim>
{
   new Claim(ClaimTypes.Role, RoleEnums.Admin),
   new Claim(CustomClaimTypes.TenantUrl, viewModel.InstitutionUrl)
};
await _userManager.AddClaimsAsync(userCreateresult.User, adminClaims);

After the above code is run, I can verify that 2 claims have been added to the AspNetUserClaims table.

However, when I run the application the below code is not working, i.e. the portion of the HTML is not displaying.

<AuthorizeView Roles="Admin">
   <Authorized>
      <li class="nav-item px-3">
          <NavLink class="nav-link" href="counter">
             <span class="oi oi-plus" aria-hidden="true"></span> Counter
          </NavLink>
      </li>
      <li class="nav-item px-3">
          <NavLink class="nav-link" href="fetchdata">
              <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
          </NavLink>
      </li>
   </Authorized>
</AuthorizeView>

I tried to add as it's suggested in the documentation, but it didn't work.

When looking at the code in the /Server/Areas/Identity/Pages/Account/Login.cshtml.cs where user's being logged in, I don't see where the token is being created.

if (ModelState.IsValid)
{
   // This doesn't count login failures towards account lockout
   // To enable password failures to trigger account lockout, set lockoutOnFailure: true
   var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
   if (result.Succeeded)
   {
       _logger.LogInformation("User logged in.");
       return LocalRedirect(returnUrl);
   }

   //more code here...
}

What to do so that, when user logs in, the token sent contains all his/her claims.

Thanks for helping.


Solution

  • I found the solution from Felipe Gavilan's course on Udemy.

    1. Create this class that implements IProfileService

      public class IdentityProfileService : IProfileService
      {
          private readonly IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory;
          private readonly UserManager<ApplicationUser> userManager;
      
          public IdentityProfileService(
              IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
              UserManager<ApplicationUser> userManager)
          {
              this.claimsFactory = claimsFactory;
              this.userManager = userManager;
          }
          public async Task GetProfileDataAsync(ProfileDataRequestContext context)
          {
              var userId = context.Subject.GetSubjectId();
              var user = await userManager.FindByIdAsync(userId);
              var claimsPrincipal = await claimsFactory.CreateAsync(user);
              var claims = claimsPrincipal.Claims.ToList();
      
              var claimsFromDb = await userManager.GetClaimsAsync(user);
              var mappedClaims = new List<Claim>();
              foreach (var claim in claimsFromDb)
              {
                  if (claim.Type == ClaimTypes.Role)
                      mappedClaims.Add(new Claim(JwtClaimTypes.Role, claim.Value));
                  else
                      mappedClaims.Add(claim);
              }
      
              claims.AddRange(mappedClaims);
              context.IssuedClaims = claims;
          }
      
          public async Task IsActiveAsync(IsActiveContext context)
          {
              var userId = context.Subject.GetSubjectId();
              var user = await userManager.FindByIdAsync(userId);
              context.IsActive = user != null;
          }
      }
      
    2. Then register the service

      services.AddIdentityServer()
              .AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
              .AddProfileService<IdentityProfileService>(); //adding this line.
      

    That's all. Now I'm getting claims embedded in the JWT token.