1

I'm working on an ASP.Net Core API project.

There are 2 controllers, controller A and B, I use Postman to pass my username and password to controller A action method, and it successfully returns a JWT. After I pass the JWT to controller B, I hope to get the username and role information of the credential which I had input into the controller A.

The code in controller B below shows the way I get the user info.

public async Task<ActionResult> Get()
        {
            var user = await _userManager.GetUserAsync(HttpContext.User);
            var role = await _userManager.GetRolesAsync(user);
            return Ok(user);
        }

But after I sent the JWT, I got null user.

enter image description here

Edit1:

ConfigureServices method in my Startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = "issuer",
                        ValidAudience = "audience",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("This is my super long security Key 123"))
                    };
                });

            services.AddDbContext<JWT_AuthContext>(option => option.UseSqlServer(Configuration.GetConnectionString("JWT_AuthContextConnection")));

        }

IdentityHostingStartup.cs:

public class IdentityHostingStartup : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((context, services) => {
                services.AddDbContext<JWT_AuthContext>(options =>
                    options.UseSqlServer(
                        context.Configuration.GetConnectionString("JWT_AuthContextConnection")));

                services.AddDefaultIdentity<JWT_AuthUser>()
                    .AddRoles<JWT_AuthRole>()
                    .AddEntityFrameworkStores<JWT_AuthContext>();
            });
        }
    }

Controller Action for JWT generation (Controller A in the previous context):

[Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private UserManager<JWT_AuthUser> _userManager;
        public AuthController(UserManager<JWT_AuthUser> userManager)
        {
            _userManager = userManager;
        }

        [HttpPost("token")]
        public async Task<ActionResult> GetToken()
        {
            var header = Request.Headers["Authorization"];
            if(header.ToString().StartsWith("Basic"))
            {
                var loginInfo = header.ToString().Substring("Basic ".Length).Trim();
                var userNameAndPassword = Encoding.UTF8.GetString(Convert.FromBase64String(loginInfo)); //username:password
                var userName = userNameAndPassword.Split(":")[0];
                var password = userNameAndPassword.Split(":")[1];
                var user = await _userManager.FindByNameAsync(userName);

                if (user != null && await _userManager.CheckPasswordAsync(user,password))
                {
                    //security key
                    string securityKey = "This is my super long security Key 123";

                    //symmetric security key
                    var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));

                    //signing credentials
                    var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature);

                    //add claims
                    var claims = new List<Claim>();

                    //create token
                    var token = new JwtSecurityToken(
                        issuer: "issuer",
                        audience: "audience",
                        expires: DateTime.Now.AddHours(1),
                        claims: claims,
                        signingCredentials: signingCredentials
                        );

                    //return token
                    return Ok(new JwtSecurityTokenHandler().WriteToken(token));
                }

            }

            return Unauthorized();
}

Controller method for getting user info (Controller B in the previous context):

[Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {

        private UserManager<JWT_AuthUser> _userManager;
        public ValuesController(UserManager<JWT_AuthUser> userManager)
        {
            _userManager = userManager;
        }

        // GET api/values
        [HttpGet]
        public async Task<ActionResult> Get()
        {
            var user = await _userManager.GetUserAsync(HttpContext.User);
            var role = await _userManager.GetRolesAsync(user);
            return Ok(user);
        }
}

Edit 2: Startup.cs Configure

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseMvc();
        }
Chen
  • 171
  • 3
  • 14
  • 1
    1. Please show your `Startup.cs`. 2. Please also include the request in which you pass the JWT to controller B – itminus Sep 18 '19 at 09:15
  • 1
    Possible duplicate of [JWT How to add custom claims and decode claims](https://stackoverflow.com/questions/52462796/jwt-how-to-add-custom-claims-and-decode-claims) – Andrei Dragotoniu Sep 18 '19 at 09:21
  • 1
    can you share your ```_userManager.GetRolesAsync(user);``` method code and also check before ```user != null``` before calling. – awais Sep 18 '19 at 13:28
  • @awais, sorry I don't get "share your _userManager.GetRolesAsync(user)", I use this piece of code directly. – Chen Sep 19 '19 at 00:41
  • @itminus, yes, I've provided extra code of my project, the way I pass the JWT to Controller B is by copying the JWT to the Postman-Authorization-Bearer Token. – Chen Sep 19 '19 at 00:43
  • @Chen Please also include the `Configure()` so that we can make sure the middlewares are registered correctly – itminus Sep 19 '19 at 00:45
  • @itminus Configure() added, plz have a check – Chen Sep 19 '19 at 01:25
  • Thanks, @AndreiDragotoniu ,I got another approach by your link – Chen Sep 19 '19 at 05:16

2 Answers2

1

The reason is that you're using UserManager<JWT_AuthUser>.GetUserAsync(user) to get the principal, which belongs to the Identity Authentication Scheme. As the document describes, this method will only read the IdentityOptions.ClaimsIdentity.UserIdClaimType claim:

Returns the user corresponding to the IdentityOptions.ClaimsIdentity.UserIdClaimType claim in the principal or null.

Note the claims issued by your GetToken() method is an empty list:

[HttpPost("token")]
public async Task<ActionResult> GetToken()
{
    ....
        //add claims
        var claims = new List<Claim>();

        //create token
        var token = new JwtSecurityToken(
            issuer: "issuer",
            audience: "audience",
            expires: DateTime.Now.AddHours(1),
            claims: claims,
            signingCredentials: signingCredentials
            );
   ....

}

Since there's no such a IdentityOptions.ClaimsIdentity.UserIdClaimType claim, you'll get a null when invoking var user = await _userManager.GetUserAsync(HttpContext.User);.

And because the user is null, it throws when invoking await _userManager.GetRolesAsync(user);

How to Fix

  1. Inject the IdentityOptions to get the UserIdClaimType:
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private UserManager<IdentityUser> _userManager;
        private readonly IdentityOptions _idOpts;

        public AuthController(UserManager<IdentityUser> userManager,IOptions<IdentityOptions> optionsAccessor)
        {
            _userManager = userManager;
            this._idOpts = optionsAccessor.Value?? new IdentityOptions();
        }

  1. Add claims before sending the token:
    //add claims
    var claims = new List<Claim>();
    claims.Add(new Claim(this._idOpts.ClaimsIdentity.UserIdClaimType, user.Id ));

    //create token
    var token = new JwtSecurityToken(
        issuer: "issuer",
        audience: "audience",
        expires: DateTime.Now.AddHours(1),
        claims: claims,
        signingCredentials: signingCredentials
     );
itminus
  • 23,772
  • 2
  • 53
  • 88
1

Another question inspired me enter link description here by the idea of holding user info inside JWT claim.

In the JWT generating action method inside Controller A, I use Usermanager to get user information of my credentials and put them into JWT claims.

var roles = await _userManager.GetRolesAsync(user);
var claims = new List<Claim>();
claims.Add( new Claim("Role", roles[0]));

After passing the JWT to controller B, this piece of code help me get the claim info:

var roleClaim = User.Claims.FirstOrDefault(x => x.Type.Equals("Role", StringComparison.InvariantCultureIgnoreCase));

Which fetch me the role

Chen
  • 171
  • 3
  • 14
  • 1
    You don't need to Add "Role" Claim in that way. Use `ClaimTypes.Role` instead. See also my previous answer here : https://stackoverflow.com/a/56354885/10091607 – itminus Sep 19 '19 at 05:32