如何在.NETCoreWebAPI (来自 JWT 令牌)中获得当前用户

经过一番努力(以及大量的教程、指南等) ,我设法建立了一个小。NET 核心 REST Web API,当存储的用户名和密码有效时,使用 Auth 控制器发出 JWT 令牌。

令牌将用户 ID 存储为子声明。

我还设置了 WebAPI,以便在方法使用 Authorize 注释时验证这些令牌。

 app.UseJwtBearerAuthentication(...)

Now my question: How do I read the user id (stored in the subject claim) in my controllers (in a Web API)?

It is basically this question (How do I get current user in ASP .NET Core) but I need an answer for a web api. And I do not have a UserManager. So I need to read the subject claim from somewhere.

102650 次浏览

You can use this method:

var email = User.FindFirst("sub")?.Value;

In my case I'm using the email as a unique value

The accepted answer did not work for me. I'm not sure if that's caused by me using .NET Core 2.0 or by something else, but it looks like the framework maps the Subject Claim to a NameIdentifier claim. So, the following worked for me:

string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

Note that this assumes the Subject sub Claim is set in the JWT and its value is the user's id.

By default, the JWT authentication handler in .NET will map the sub claim of a JWT access token to the System.Security.Claims.ClaimTypes.NameIdentifier claim type. [Source]

There is also a discussion thread on GitHub where they conclude this behavior is confusing.

If you use Name to store the ID here:

var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};

In each controller method you can get the ID of the current user by:

var claimsIdentity = this.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;

you can do this using.

User.Identity.Name

It seems a lot of people are looking at this question so I would like to share some more information I learned since I asked the question a while back. It makes some things more clear (at least for me) and wasn't so obvious (for me as .NET newbie).

As Marcus Höglund mentioned in the comments:

It should be the same for "web api"..In ASP.NET Core Mvc and Web Api are merged to use the same controller.

That's definitely true and absolutely correct.


Because it is all the same across .NET and .NET Core.

Back than I was new to .NET Core and actually the full .NET world. The important missing information was that in .NET and .NET Core all the authentication can be trimmed down to System.Security.Claims namespace with its ClaimsIdentity, ClaimsPrinciple and Claims.Properties. And therefore it is used in both .NET Core controller types (API and MVC or Razor or ...) and is accessible via HttpContext.User.

An important side note all of the tutorials missed to tell.

So if you start doing something with JWT tokens in .NET don't forget to also get confident with ClaimsIdentity, ClaimsPrinciple and Claim.Properties. It's all about that. Now you know it. It was pointed out by Heringer in one of the comments.


ALL the claim based authentication middlewares will (if correctly implemented) populate the HttpContext.User with the claims received during authentication.

As far as I understand now this means one can safely trust on the values in the HttpContext.User. But wait a bit to know what to mind when selecting middleware. There are a lot of different authentication middleware already available (in addition to .UseJwtAuthentication()).

With small custom extension methods you can now get the current user id (more accurate the subject claim) like that

 public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value; }

Or you use the version in the answer of Ateik.


BUT WAIT: there is one strange thing

The next thing that confused me back than: according to the OpenID Connect spec I was looking for "sub" claim (the current user) but couldn't find it. Like Honza Kalfus couldn't do in his answer.

Why?

Because Microsoft is "sometimes" "a bit" different. Or at least they do a bit more (and unexpected) things. For example the official Microsoft JWT Bearer authentication middleware mentioned in the original question. Microsoft decided to convert claims (the names of the claims) in all of their official authentication middleware (for compatibility reasons I don't know in more detail).

You won't find a "sub" claim (though it is the single one claim specified by OpenID Connect). Because it got converted to these fancy ClaimTypes. It's not all bad, it allows you to add mappings if you need to map different claims into a unique internal name.

Either you stick with the Microsoft naming (and have to mind that when you add/use a non Microsoft middleware) or you find out how to turn the claim mapping of for the Microsoft middleware.

In case of the JwtBearerAuthentication it is done (do it early in StartUp or at least before adding the middleware):

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

If you want to stick with the Microsoft namings the subject claim (don't beat me, I am not sure right now if Name is the correct mapping):

    public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase))?.Value; }

Note that the other answers use the more advanced and way more convenient FindFirst method. Though my code samples show it without those you may should go with them.

So all your claims are stored and accessible (via one name or the other) in the HttpContext.User.


But where is my token?

I don't know for the other middleware but the JWT Bearer Authentication allows to save the token for each request. But this needs to be activated (in StartUp.ConfigureServices(...).

services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options => options.SaveToken = true);

The actual token (in all it's cryptic form) as string (or null) can then be accessed via

HttpContext.GetTokenAsync("Bearer", "access_token")

There has been an older version of this method (this works for me in .NET Core 2.2 without deprecated warning).

If you need to parse and extract values from this string may the question How to decode JWT token helps.


Well, I hope that summary helps you too.

I used the HttpContext and it works well:

var email = string.Empty;
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
email = identity.FindFirst(ClaimTypes.Name).Value;
}

In my case I set ClaimTypes.Name to unique user email before JWT token generation:

claims.Add(new Claim(ClaimTypes.Name, user.UserName));

Then I stored unique user id to ClaimTypes.NameIdentifier:

claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));

Then in the controller's code:

int GetLoggedUserId()
{
if (!User.Identity.IsAuthenticated)
throw new AuthenticationException();


string userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;


return int.Parse(userId);
}

Mine worked using the following code in .net core 5 web api

User.Claims.First(x => x.Type == "id").Value;

asp.net core identity get user id

 public async Task<IActionResult> YourMethodName()
{
var userId =  User.FindFirstValue(ClaimTypes.NameIdentifier) // will give the user's userId
var userName =  User.FindFirstValue(ClaimTypes.Name) // will give the user's userName


ApplicationUser applicationUser = await _userManager.GetUserAsync(User);
string userEmail = applicationUser?.Email; // will give the user's Email
}

.net core identity get user id

 public static class ClaimsPrincipalExtensions
{
public static T GetLoggedInUserId<T>(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));


var loggedInUserId = principal.FindFirstValue(ClaimTypes.NameIdentifier);


if (typeof(T) == typeof(string))
{
return (T)Convert.ChangeType(loggedInUserId, typeof(T));
}
else if (typeof(T) == typeof(int) || typeof(T) == typeof(long))
{
return loggedInUserId != null ? (T)Convert.ChangeType(loggedInUserId, typeof(T)) : (T)Convert.ChangeType(0, typeof(T));
}
else
{
throw new Exception("Invalid type provided");
}
}


public static string GetLoggedInUserName(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));


return principal.FindFirstValue(ClaimTypes.Name);
}


public static string GetLoggedInUserEmail(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));


return principal.FindFirstValue(ClaimTypes.Email);
}
}