59

After a lot of struggling (and a lot of tuturials, guides, etc) I managed to setup a small .NET Core REST Web API with an Auth Controller issuing JWT tokens when stored username and password are valid.

The token stores the user id as sub claim.

I also managed to setup the Web API to validate those tokens when a method uses the Authorize annotation.

 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.

monty
  • 5,629
  • 11
  • 44
  • 74

7 Answers7

72

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.

Honza Kalfus
  • 3,987
  • 2
  • 24
  • 36
  • 3
    Or slightly shorter: [`User.FindFirstValue(ClaimTypes.NameIdentifier)`](https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.principalextensions.findfirstvalue?view=aspnetcore-2.2) – Seafish Aug 10 '19 at 07:28
  • 1
    @Seafish Be sure to make a null check in that case as well. – Honza Kalfus Feb 15 '20 at 17:45
42

You can use this method:

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

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

Ateik
  • 2,348
  • 4
  • 34
  • 58
  • 3
    Thanks, should be marked as accepted answer! For the user name: User.Identity.Name. User is a property of Microsoft.AspNetCore.Mvc.ControlerBase and it's type is System.Security.Claims.ClaimsPrincipal. Just adding. – heringer Mar 06 '18 at 17:18
  • 5
    another way could be: string sub = HttpContext?.User.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier).Value; – monty Mar 12 '18 at 07:08
  • Or User.FindFirstValue(ClaimTypes.Name) – Gustavo Piucco Jan 31 '19 at 20:00
  • 4
    Note that if you aren't using `[Authorize]` attributes, that `User` may be a sort of empty user where `User.Identity.IsAuthenticated` is false. So watch out for that. – Simon_Weaver Feb 04 '19 at 06:50
31

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.

marc_s
  • 675,133
  • 158
  • 1,253
  • 1,388
monty
  • 5,629
  • 11
  • 44
  • 74
  • 1
    Having the claims in the actual token match up exactly to what is in available in the controller makes so much sense. I think your answer should begin with this in Startup.cs: JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); Then this in the Controller.cs: var userSub = User.FindFirst("sub")?.Value; This was the only answer which fully explained the problem without just linking to a confusing thread on GitHub. Well done. – jezpez Apr 28 '19 at 00:36
  • I wish I had read this answer earlier. Wasted an hour to find out how .Net changed the claim type. I explicitly named the claim "sub" and I just I couldn't figure out why "jti" is working but not the "sub". This can get many people new to JWT in .Net Core confused. Your answer clarifies it. – farshad Apr 06 '20 at 19:22
24

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;
ViRuSTriNiTy
  • 4,581
  • 1
  • 27
  • 52
ImpoUserC
  • 549
  • 4
  • 13
6

you can do this using.

User.Identity.Name

6

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;
}
Wariored
  • 1,070
  • 11
  • 18
5

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);
        }
nikolai.serdiuk
  • 602
  • 8
  • 11