JWT Authentication - UserManager.GetUserAsync returns null

12,182

Solution 1

UserManager.GetUserAsync internally uses UserManager.GetUserId to retrieve the user id of the user which is then used to query the object from the user store (i.e. your database).

GetUserId basically looks like this:

public string GetUserId(ClaimsPrincipal principal)
{
    return principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType);
}

So this returns the claim value of Options.ClaimsIdentity.UserIdClaimType. Options is the IdentityOptions object that you configure Identity with. By default the value of UserIdClaimType is ClaimTypes.NameIdentifier, i.e. "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier".

So when you try to use UserManager.GetUserAsync(HttpContext.User), where that user principal has a UserID claim, the user manager is simply looking for a different claim.

You can fix this by either switchting to the ClaimTypes.NameIdentifier:

new ClaimsIdentity(new[]
{
    new Claim(ClaimTypes.Name, user.UserName),
    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
})

Or you configure Identity properly so it will use your UserID claim type:

// in Startup.ConfigureServices
services.AddIdentity(options => {
    options.ClaimsIdentity.UserIdClaimType = "UserID";
});

Solution 2

When you create Claims, you just need to do like:

 List<Claim> claims = new()
 {
    new Claim(ClaimTypes.NameIdentifier, user.Id),  // This line is important.
    new Claim(ClaimTypes.Email, user.Email),
    new Claim(JwtRegisteredClaimNames.Jti, jti)
 };
Share:
12,182

Related videos on Youtube

Alex Herman
Author by

Alex Herman

FREELANCER PROFILE — https://talent.hubstaff.com/profiles/aliaksei-herman ASP.NET MVC + jQuery, Knockout .NET Core + SPA apps using React, Angular, Vue My 5 projects on GitHub: feasible-ui — react-based UI toolkit written from scratch | not based on other UI libraries, serves as a boilerplate providing essential controls eixample — multi-tenant ASP.NET Core architecture: EF Core + PostgreSQL + React, Vue, Angular eixample_webapi2 — same as above, only for proprietary ASP.NET: EF + SQLServer + React, Vue, Angular pern-multitenancy — multi-tenant PERN architecture mern-multitenancy — multi-tenant MERN architecture

Updated on October 19, 2021

Comments

  • Alex Herman
    Alex Herman over 2 years

    In AuthController when authenticating I create a few Claims - UserID is one of them.

    ...
    Subject = new ClaimsIdentity(new[]
    {
      new Claim(ClaimTypes.Name, user.UserName),
      new Claim("UserID", user.Id.ToString()),
    })
    

    When Angular app makes request I am able to fetch UserID in another controller

    Claim claimUserId = User.Claims.SingleOrDefault(c => c.Type == "UserID");
    

    The ControllerBase.User instance holds .Identity object which in turn holds Claims collection.

    • Identity.IsAuthenticated equals True.

    • Identity.Name holds admin string (name of the relevant user).

    If I try to fetch user like this:

    var user = await UserManager.GetUserAsync(HttpContext.User)
    

    the user is null.

    Perhaps, I forgot to add some extra claim?

    Or maybe, once I'm using JWT - I should override the default UserManager functionality so it fetches user by claim which holds UserID?

    Or maybe there's a better approach?


    Additional info:

    The Identity is registered as follows

    services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddEntityFrameworkStores<AppDbContext>()
        .AddDefaultTokenProviders();
    

    ApplicationUser.Id field is of bigint (or in C# of long) type

    Also, I create users in EF Seed Data with UserManager which is resolved using ServiceProvider

    _userManager = scope.ServiceProvider.GetService<UserManager<ApplicationUser>>();
        ...
            adminUser.PasswordHash = new PasswordHasher<ApplicationUser>().HashPassword(adminUser, "123qwe");
            _userManager.CreateAsync(adminUser);
    
    • Alex Riabov
      Alex Riabov almost 6 years
      Could you share how you register Identity?
    • Alex Herman
      Alex Herman almost 6 years
      hi @AlexRiabov, just answered your question, the code under Additional Info section.
  • Rosdi Kasim
    Rosdi Kasim over 5 years
    Alternatively..., if userId is not available, one can use userManager.FindByNameAsync(username) instead if that fits the business requirement.
  • mwilson
    mwilson about 4 years
    Simply doing new Claim(ClaimTypes.NameIdentifier, user.Id) worked for me. Previously had this as user.UserName. Didn't realize the "name identifier" would be an Id. Who woulda thunk.
  • poke
    poke about 4 years
    @mwilson How you use the claims mostly depends on your application. There are different ways to approach this, and it also depends on what your id is.
  • eaglei22
    eaglei22 about 3 years
    Great solution. In my case, the Startup.ConfigureServices solution worked for me. Setting a breakpoint in the controller and navigating through the User->claims helped me realize which claim type my organization was using to get the userid. Then setting options.ClaimsIdentity.UserIdClaimType = ClaimTypes.[option] did it for me.
  • ackh
    ackh over 2 years
    UserManager.GetUserAsync eventually calls the method FindByIdAsync of the IUserStore implementation which is helpful to know if you need to debug a custom IUserStore implementation.