Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.Net Core JWT Login not setting HttpContext.User.Claims

I have the following for my login code, and another method to retrieve the user ID in another call.

        // POST: /api/Account/Login
    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Login([FromBody]LoginViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user != null)
            {
                if (!await _userManager.IsEmailConfirmedAsync(user))
                {
                    ModelState.AddModelError(string.Empty, 
                                "You must have a confirmed email to log in.");
                    return BadRequest(Errors.AddErrorToModelState("Email", "You must have a confirmed email to log in.", ModelState));
                }
            }

            var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);

            if (result.Succeeded)
            {
                // IS THIS NEEDED? --> // HttpContext.User = await _userClaimsPrincipalFactory.CreateAsync(user);
                var tokens = _antiforgery.GetAndStoreTokens(HttpContext);

                var identity = _jwtFactory.GenerateClaimsIdentity(model.Email, user.Id);
                _logger.LogInformation(1, "User logged in.");
                var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, model.Email, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
                return new OkObjectResult(jwt);
            }

            if (result.RequiresTwoFactor)
            {
                //return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
            }

            if (result.IsLockedOut)
            {
                string message = "User account locked out.";
                _logger.LogWarning(2, message);
                return BadRequest(Errors.AddErrorToModelState("Email", message, ModelState));
            }

            if (result.IsNotAllowed)
            {
                string message = "User account is not allowed to sign in.";
                _logger.LogWarning(2, message);
                return BadRequest(Errors.AddErrorToModelState("Email", message, ModelState));
            }

            return BadRequest(Errors.AddErrorToModelState("", "Sign in failed.", ModelState));
        }

        return BadRequest(Errors.AddErrorToModelState("", "", ModelState));            
    }

    public string GetUserId()
    {
        string username = string.Empty;

        ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal;  

        if (HttpContext.User != null)
        {
            var id = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);

            if (id != null)
            {
                username = id.Value;
            }
        }

        return username;
    }

This is the code for GenerateJwt. (Are these fields standard? I see other fields being set in other code, such as in https://code-maze.com/authentication-aspnetcore-jwt-1/.)

  public static async Task<string> GenerateJwt(ClaimsIdentity identity, IJwtFactory jwtFactory,string userName, JwtIssuerOptions jwtOptions, JsonSerializerSettings serializerSettings)
  {
    var response = new
    {
      id = identity.Claims.Single(c => c.Type == "id").Value,
      auth_token = await jwtFactory.GenerateEncodedToken(userName, identity),
      expires_in = (int)jwtOptions.ValidFor.TotalSeconds
    }; // see GenerateEncodedToken pasted below.

    return JsonConvert.SerializeObject(response, serializerSettings);
  }

    public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity)
    {
        var claims = new[]
     {
             new Claim(JwtRegisteredClaimNames.Sub, userName),
             new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
             new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
             identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Rol),
             identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Id)
         };

        // 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);

        return encodedJwt;
    }

These are the relevant lines in ConfigureServices.

  // jwt wire up
  // Get options from app settings
  var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));

  // Configure JwtIssuerOptions
  services.Configure<JwtIssuerOptions>(options =>
  {
    options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
    options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
    options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
  });

  var tokenValidationParameters = new TokenValidationParameters
  {
    ValidateIssuer = true,
    ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],

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

    ValidateIssuerSigningKey = true,
    IssuerSigningKey = _signingKey,

    RequireExpirationTime = false,
    ValidateLifetime = true,
    ClockSkew = TimeSpan.Zero
  };

  services.AddAuthentication(options =>
  {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

  }).AddJwtBearer(configureOptions =>
  {
    configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
    configureOptions.TokenValidationParameters = tokenValidationParameters;
    configureOptions.SaveToken = true;
    configureOptions.RequireHttpsMetadata = false;
  });

  // api user claim policy
  services.AddAuthorization(options =>
  {
    options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
  });

  // add identity
  var builder = services.AddIdentity<AppUser, AppRole>(o =>
  {
    // configure identity options
    o.Password.RequireDigit = false;
    o.Password.RequireLowercase = false;
    o.Password.RequireUppercase = false;
    o.Password.RequireNonAlphanumeric = false;
    o.Password.RequiredLength = 6;
  })
  .AddSignInManager<SignInManager<AppUser>>()
  .AddEntityFrameworkStores<AppIdentityDbContext>()
  .AddDefaultTokenProviders();

I'm using the following to pass my token from Angular.

  private authHeader(): HttpHeaders {
  let headers = new HttpHeaders();
  if (isPlatformBrowser(this.platformId)) {
    headers = headers.set('Content-Type', 'application/json');
    let authToken = this.windowRefService.nativeWindow.localStorage.getItem('auth_token');
    headers = headers.set('Authorization', authToken);
    headers = headers.set('X-XSRF-TOKEN', this.getCookie('XSRF-TOKEN'));
  }

  return headers;
}

  protected jsonAuthRequestOptions = { headers: this.authHeader() };

public loggedInAs(): Observable<string> {
    return this.http.post<any>(this.baseUrl + "api/Account/GetUserId", { } , this.jsonAuthRequestOptions)
    .map(data => {
        if (data) {
            return data;
        }

        return '';
    })
    .catch(this.handleError);
}

Does JWT authentication automatically set HttpContext.User? Or do I have to do that explicitly?

My succeeding call fails. (User has no claims, though HttpContext.User is not null.) I don't know whether the problem is with relying on HttpContext.User, or whether there is something wrong with the token generation / consumption.

like image 220
Jonas Arcangel Avatar asked Nov 10 '18 10:11

Jonas Arcangel


1 Answers

Had a similar problem, Claims from token did not pass to HttpContext.User.Identity.Claims.

In my case, changing the order of adding services helped.

In startup.cs I've got now:

services.AddIdentity();
services.AddAuthentication();
like image 89
Maciej Grala Avatar answered Nov 05 '22 01:11

Maciej Grala