33

I am trying to use JWT for authentication mechanism in ASP.NET Core Web API project. Suppose this project has not MVC part and does not use cookie authentication. I have created my code based on this guide.

Login works good and protection with [Authorize] attribute works ok but User.Identity.Name is null. How can I fix this?

My code:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],

        ValidateAudience = true,
        ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],

        ValidateIssuerSigningKey = true,
        IssuerSigningKey = _signingKey,

        RequireExpirationTime = true,
        ValidateLifetime = true,

        ClockSkew = TimeSpan.Zero
    };

    app.UseJwtBearerAuthentication(new JwtBearerOptions
    {
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        TokenValidationParameters = tokenValidationParameters,
        AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme
    });

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

    [HttpPost]
    [AllowAnonymous]
    [Route("Login")]
    public async Task<IActionResult> Login([FromForm] ApplicationUser applicationUser)
    {
        //assume user/pass are checked and are ok

        _logger.LogInformation(1, "API User logged in.");
        var user = await _userManager.FindByNameAsync(applicationUser.UserName);
        var roles = await _userManager.GetRolesAsync(user);

        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.Sub, applicationUser.UserName),
            new Claim(ClaimTypes.NameIdentifier, applicationUser.UserName),
            new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
            new Claim(JwtRegisteredClaimNames.Iat,
                    ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(),
                    ClaimValueTypes.Integer64),
                    new Claim("Claim", "Value")
        };

        if (roles != null)
            foreach (var role in roles)
                claims.Add(new Claim("role", role));

        // Create the JWT security token and encode it.
        var jwt = new JwtSecurityToken(
            issuer: _jwtOptions.Issuer,
            audience: _jwtOptions.Audience,
            claims: claims,
            notBefore: _jwtOptions.NotBefore,
            expires: _jwtOptions.Expiration,
            signingCredentials: _jwtOptions.SigningCredentials);

        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

        // Serialize and return the response
        var response = new
        {
            access_token = encodedJwt,
            expires_in = (int)_jwtOptions.ValidFor.TotalSeconds
        };

        var json = JsonConvert.SerializeObject(response, _serializerSettings);
        return new OkObjectResult(json);
    }
Afshar Mohebi
  • 10,479
  • 17
  • 82
  • 126

3 Answers3

44

in your claims (second code snippet) I can only see this:

new Claim(ClaimTypes.NameIdentifier, applicationUser.UserName),

but you need to add this:

new Claim(ClaimTypes.Name, applicationUser.UserName),

then User.Identity.Name should contain the username.

jps
  • 20,041
  • 15
  • 75
  • 79
15

Another option is to set the namespace for the JwtRegisteredClaimNames.Sub in the tokenValidationParameters. This will let you continue to use the standard:

var tokenValidationParameters = new TokenValidationParameters
{
    // Ensure that User.Identity.Name is set correctly after login
    NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",

    ... existing code ...
}

Update: Diogo Barros left a comment on my blog about this topic:

"Hello,

Thank you for your help. This worked for me. For more consistency and safety, you can use the ClaimTypes.NameIdentifier (in the System.Security.Claims namespace), instead of the hardcoded string."

I've changed our code to use the built-in ClaimTypes enumeration as it is a bit more elegant than using the namespace string.

Michael Earls
  • 1,467
  • 1
  • 15
  • 25
  • Using the "sub" field of the token looks more standard than the accepted answer, and also reduces the token size a little :-) – youen Nov 28 '17 at 14:09
  • 1
    To use the constant: `NameClaimType = ClaimTypes.NameIdentifier`. `ClaimTypes` comes from `System.Security.Claims` – Telmo Marques Jun 15 '22 at 11:17
1

Adding app.UseAuthentication(); to

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 

fixed my issue.

buddemat
  • 4,552
  • 14
  • 29
  • 49