0

i get always null as User in any controller

var usr = User as ClaimsPrincipal;

i've configired my app in this way:

var builder = WebApplication.CreateBuilder(args);

ConfigurationManager configuration = builder.Configuration;
builder.Services.AddDbContext<CustomIdentityDbContext>(options =>
{
    options.UseSqlServer(configuration.GetConnectionString("SqlServerConnection")).EnableSensitiveDataLogging();

   }
);

//config my custom identity
builder.Services.AddIdentity<WSA_UTENTE, WSA_RUOLI>(options =>
{
    options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<CustomIdentityDbContext>()
.AddDefaultTokenProviders();


//add jwt autentication middleware
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});

then i'm able to generate a token

private JwtSecurityToken GetToken(IEnumerable<Claim> authClaims)
        {
            var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));

            var token = new JwtSecurityToken(
                issuer: _configuration["Jwt:ValidIssuer"],
                audience: _configuration["Jwt:ValidAudience"],
                expires: DateTime.Now.AddMinutes(String.IsNullOrEmpty(_configuration["Jwt:AccessTokenLifetime"]) ? int.Parse(_configuration["Jwt:AccessTokenLifetime"]) : 525600),
                claims: authClaims,
                signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
                );

            return token;
        }

between the prominent claims:

"sub: 8001ac54-12c0-4d18-f5a7-08db0445001d"
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: myusername"

whenever i call any controller var usr = User as ClaimsPrincipal; is always null

do i need to do something like HttpContext.SignInAsync to populate the HttpContext.Useror the jwt middleware do all the dirty job automatically?

jps
  • 20,041
  • 15
  • 75
  • 79
pinale
  • 2,060
  • 6
  • 38
  • 72
  • In my app, I had to assign the issuer, audience and the expiry to `null` values, `ValidIssuer` and `ValidAudience` to `null` values, and and set `ValidateIssuer`, `ValidateAudience`, `ValidateLifetime` to false. Try enabling and disabling properties until it works – Codingwiz Mar 13 '23 at 13:34
  • @Codingwiz it was a typo in the message, doesn't work – pinale Mar 13 '23 at 13:35

1 Answers1

0

EDIT: don't forget to add builder.Services.AddHttpContextAccessor();, app.UseAuthentication(); and app.UseAuthorization(); in Program.cs

There may be an issue in how you store and retrieve the JWT.

I store mine as a string in a cookie and then retrieve the cookie value from each request as a JWT.

You can also store it in LocalStorage, but you will have to send the JWT as a bearer token in the authorization header for each request to the server.

// saving JWT in a cookie in Razor page controller
string jwtSecurityTokenHandler = new JwtSecurityTokenHandler.WriteToken(GetToken(authClaims));

Response.Cookies.Append("jwtCookieName", jwtSecurityTokenHandler, new CookieOptions
{
    HttpOnly = true,
    IsEssential = true,
    Secure = true,
    SameSite = SameSiteMode.Strict,

    // need to specify an expiry date for persistency
    // otherwise cookie will default to Session and will be cleared on browser closed
    Expires = new DateTimeOffset(DateTime.Now).AddDays(7)
});

// get cookie from HttpContext
public class UserAuth
{
    private readonly IHttpContextAccessor? _httpContextAccessor; // builder.Services.AddHttpContextAccessor(); --> add default implementation of HttpContextAccessor in Program.cs to access http context in a service class
    public UserAuth(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;

    // use it like this: new UserAuth().GetClaimsFromContext();
    public IEnumerable<Claim> GetClaimsFromContext()
    {
        if (_httpContextAccessor is null || _httpContextAccessor.HttpContext is null)
            return Enumerable.Empty<Claim>();

        if (_httpContextAccessor.HttpContext.User.Identity is not ClaimsIdentity claimsIdentity || claimsIdentity.Claims is null || !claimsIdentity.Claims.Any())
            return Enumerable.Empty<Claim>();

        IEnumerable<Claim> listUserClaims = claimsIdentity.Claims ?? Enumerable.Empty<Claim>();
    }
}

// JWT configuration in Program.cs
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters {...};

// authenticate user using JWT stored in cookie
options.Events = new JwtBearerEvents
{
    OnMessageReceived = context =>
    {
        context.Token = context.Request.Cookies["jwtCookieName"];
        return Task.CompletedTask;
    }
};

Here is more ways to get a JWT from a cookie found by @user1269009

Ref. 1

Ref. 2

Codingwiz
  • 192
  • 2
  • 14
  • actually i dont store the token, should `options.SaveToken = true` do all the dirty job?; anyway do i really need to store it if each http call i do has onboard the authorization token header? – pinale Mar 13 '23 at 14:47
  • You have to store the token in client side and cache it in server side. `options.SaveToken = true` will save to the server, but only after authorization has been successful. And for the authorization to work, you need to send it to the client (as a cookie, or save in localstorage) and then send that saved JWT to the server for each request. – Codingwiz Mar 13 '23 at 14:51
  • *"anyway do i really need to store it if each http call i do has onboard the authorization token header"* And how do you send the JWT to the server ? – Codingwiz Mar 13 '23 at 14:55
  • 1
    The JWT needs to be received from server, stored locally in client (or not at all if it is not important) and the client has to send JWT in each request to server. – Codingwiz Mar 13 '23 at 14:57
  • i'm trying it in a not realworld scenario, i'm trying it via POSTMAN, on each request towards the api i attach the `Authorization: bearer xxxxxx...` header. what i don't understand is if the `HttpContext.User` is automatically populated when the http request "landing/land" on the endpoint, if so there is certainly something wrong in my configuration – pinale Mar 13 '23 at 15:03
  • If using Postman, then a simple bearer authorization header should work. It has to contain the string value of `new JwtSecurityTokenHandler.WriteToken(GetToken(authClaims))` and be sent like this: `{ "Authorization": "Bearer MyToken" }` where MyToken is the string value of JWT – Codingwiz Mar 13 '23 at 15:21
  • it is exactly in this way! there is a little news: after adding `builder.Services.AddHttpContextAccessor();` the HttpContext.User is not null anymore but is `authenticated=false` and the name is empty, a little step ahead – pinale Mar 13 '23 at 15:26
  • The `Name` is empty because you did not add a value for `ClaimTypes.Name` or misconfigured how you add new claims. – Codingwiz Mar 13 '23 at 16:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/252510/discussion-between-codingwiz-and-pinale). – Codingwiz Mar 14 '23 at 13:35