JWT Authentication - UserManager.GetUserAsync returns null

Alex Herman picture Alex Herman · Jul 1, 2018 · Viewed 8.6k times · Source

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);

Answer

poke picture poke · Jul 1, 2018

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";
});