Last night I got stuck trying to fix a bug in a REST API that really drove me nuts, so I’ll share in the hopes it will save someone else a headache.
The service uses JWTs for authentication, and are issued in an authentication controller, while the data is provided by another controller. I have a CreateToken method that creates an instance of ApplicationUser
for the requested user, and adds some claims.
var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Email, user.Email) }.Union(userClaims);
I’m sure this looks familiar if you’ve used JWTs in .NET. The method creates a valid token, and returns it to the user. The user can then take that token and pass it along to a different controller for authentication. In my case, I had customized the ApplicationUser
to include a property called TenantID, and in the controller, I needed to create an instance of the ApplicationUser
, and use the TenantID.
var user = await _userManager.GetUserAsync(User);
The problem was that the GetUserAsync(User) always returned null. After screwing around with it for way too long, I went to GitHub to see what GetUserAsync was actually doing.
https://github.com/aspnet/Identity/blob/dev/src/Core/UserManager.cs
It calls GetUserID
, and passes in the ClaimsPrincipal
.
public virtual string GetUserId(ClaimsPrincipal principal) { if (principal == null) { throw new ArgumentNullException(nameof(principal)); } return principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType); }
It wants to find the first claim associated with the UserIdClaimType….Which is what?
JwtRegisteredClaimNames.Sub
When I was creating the token, I was giving the Sub claim the user’s username, but ASP.NET identity was searching the database for the Id field in the AspNetUsers
table in SQL.
UGH.
So I changed the assignment when the token is created, and everything seems to work fine now.
var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id), new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Email, user.Email) }.Union(userClaims);