Search code examples
asp.net-identityclaims-based-identityclaimsidentityserver4

How AspNet Identity generates user claims


I have a scenario with a Single Page Application implemented with Typescript that uses oidc-client to get id_token and access_token from a AspNet Core application that uses IdentityServer4.

At the identity server I am also using Identity with EntityFramework, so that I can retrieve User and their profile to generate claims, etc.

I am facing a strange situation where I am for example updating the FirstName for a user, it gets updated properly in database but when generating the user level claims (given_name, family_name, name and email) the given_name is not updated with the new name therefore the id_token issued does not contain the updated information in the claims.

I have something like this:

public class AspNetIdentityProfileService : IProfileService
{
  private readonly UserManager<User> _userManager;
  private readonly IUserRepository _userRepository;

  public AspNetIdentityProfileService(UserManager<User> userManager, IUserRepository userRepository)
  {
     _userManager = userManager;
     _userRepository = userRepository;
  }

  // some code where I generate a List<Claim> calling below method

  private async Task<IEnumerable<Claim>> GetUserLevelClaimsAsync(User user)
  {
     //user contains the updated info because it comes from DB.
     List<Claim> userClaims = new List<Claim>();
     var standardClaims = await _userManager.GetClaimsAsync(user); //has old values
     userClaims.AddRange(standardClaims);
     userClaims.Add(new Claim(JwtClaimTypes.Role, user.Role.ToString().ToLowerInvariant()));
     return userClaims;
  }
}

Why is the await _userManager.GetClaimsAsync(user); ignoring the updated information for the user? Where is getting the old information from? I have restarted the application, deleting browser cookies in case it was stored at the cookie that the identity server issues for single-sign on purposes.. but I still don't know where the old information is cached.

UPDATE 1: This is the User model.

public class User : IdentityUser
{
    //Extend properties
    public DateTime CreatedOn { get; set; }
    public DateTime ModifiedOn { get; set; }
    public Guid? PrimaryBusinessId { get; set; }
    public Business PrimaryBusiness { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName
    {
        get
        {
            return string.Join(" ", FirstName, LastName);
        }
    }

    public DateTime? LastLoggedInOn { get; set; }

    public UserRole Role { get; set; }

    public List<UserBusiness> UserBusinesses { get; set; }
}

UPDATE 2: There is an obvious workaround, which is to manually add the claims from the User properties instead relying on the Identity's UserManager. But the question remains, why userManager doesn't retrieve the updated values in DB? and where is it finding the retrieved old values?

    private IEnumerable<Claim> GetUserLevelClaims(User user)
    {
        List<Claim> userClaims = new List<Claim>();
        //var standardClaims = await _userManager.GetClaimsAsync(user); //does not retrieve updated fields
        userClaims.Add(new Claim(JwtClaimTypes.Name, user.FullName));
        userClaims.Add(new Claim(JwtClaimTypes.FamilyName, user.LastName));
        userClaims.Add(new Claim(JwtClaimTypes.GivenName, user.FirstName));
        userClaims.Add(new Claim(JwtClaimTypes.Email, user.Email));
        userClaims.Add(new Claim(JwtClaimTypes.Role, user.Role.ToString().ToLowerInvariant()));
        return userClaims;
    }

Solution

  • You need to tell the IProfileService where to get the claims from.

    Basically userManager from AspNetCore.Identity will only get the associated claims for that user. That is, IdentityUser.Claims. The "old values" still exist in the AspNetUserClaims table. If you update the associated entity for that user for whatever claim in the table, then IProfileService will issue those updated claims since you are relying on userManager to fetch your claims.

    Basically to find out what claims belong to a user you can see that there is the 'root table' called AspNetUsers. This table has an associated claims called table called AspNetUserClaims. This table just maps the users and their 1-M claims. In short, editing a claims value in that table is what you need to do.

    Feel free to add more claims to the in the IProfileService in GetProfileDataAsync. It is quite common to further add claims based on the table entity itself. as you have demonstrated in your update #2