I'm trying to access the user Id from the token but everything I try returns null. The generated token has the necessary information so I don't think it's the token generation.
This is the part creates the token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(ClaimTypes.NameIdentifier, existingAppUser.Id),
new Claim("id", existingAppUser.Id),
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return new AuthenticationResult()
{
Token = tokenHandler.WriteToken(token)
};
When I decode the generated token I can see all of the claims in the token but I can't access it on the project.
This is the part trying to access the name identifier or the id claims
var claimsList = _httpContextAccessor.HttpContext.User.Claims.ToList();
var identityName = _httpContextAccessor.HttpContext.User.Identity.Name;
var nameId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var id = _httpContextAccessor.HttpContext.User.FindFirst(x => x.Type == "id")?.Value;
This is the JWT Configuration from Startup
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.SaveToken = true;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret)),
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuer = false
};
});
services.AddAuthorization();
services.AddHttpContextAccessor();
This is the class I'm trying to access it from
public class CurrentUserService : ICurrentUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentUserService( IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string UserId { get => _httpContextAccessor.HttpContext.User.Claims.Single(x => x.Type == "id").Value; }
public string GetUserId()
{
var claimsList = _httpContextAccessor.HttpContext.User.Claims.ToList();
var identityName = _httpContextAccessor.HttpContext.User.Identity.Name;
var nameId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var id = _httpContextAccessor.HttpContext.User.FindFirst(x => x.Type == "id")?.Value;
return "123";
}
}
I don't know what I am missing here. How do I get the userId from the token?
Well It turns out I forgot to put
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
in the necessary controllers.
Related
I want to use claim base Authorization in ASP.NET CORE Web API, I think my code is true but, it does not work for me and gives me the error 403 forbidden however I use the Right Token which has the right claim value in my AspNetUserClaims table
here is my Program.cs code
//Get Connection String
builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseNpgsql(builder.Configuration["connection:connectionString"]));
builder.Services.Configure<ApplicationSettings>(builder.Configuration.GetSection("ApplicationSettings"));
builder.Services.AddMvc();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<AppDbContext>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("VIEW",
policy => policy.RequireClaim("VIEW"));
});
builder.Services.Configure<IdentityOptions>(options =>
options.User.RequireUniqueEmail = true);
//JWT Token Setup
var key = Encoding.UTF8.GetBytes(builder.Configuration["ApplicationSettings:JWT_Secret"]);
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x => {
x.RequireHttpsMetadata = false;
x.SaveToken = false;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
here is my UsersLoginController when the user login, this controller generates the Token after successful login
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login(LoginModel model)
{
var user = await _userManager.FindByNameAsync(model.UserName);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
//Get role assigned to the user
var role = await _userManager.GetRolesAsync(user);
IdentityOptions _options = new IdentityOptions();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserID",user.Id.ToString()),
new Claim(_options.ClaimsIdentity.RoleClaimType,role.FirstOrDefault())
}),
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.JWT_Secret)), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
return BadRequest(new { message = "Username or password is incorrect." });
}
}
here is my UsersProfileDetailsController
[HttpGet]
[Authorize(Policy = "VIEW")]
public async Task<Object> GetUserProfile()
{
string userId = User.Claims.First(c => c.Type == "UserID").Value;
var user = await _userManager.FindByIdAsync(userId);
var role = await _userManager.GetRolesAsync(user);
return new
{
user.Id,
user.FirstName,
user.LastName,
user.Email,
user.UserName,
user.PhotoPath,
role,
};
}
Now when I login with Admin User which has the right Claimvalue VIEW in the Table AspNetUserClaims, it generates the Token for me but when I use this Token to access UserDetails from UsersProfileDetailsController it gives me the code 403 forbidden.
Can anyone help me with how to implement Claims base Authorization and what are my problems?
I use Postman for testing.
because you configured authoriztion as below:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("VIEW",
policy => policy.RequireClaim("VIEW"));
});
If you Press F12 and go to definition,you'll find the explaination of
the method
you added [Authorize(Policy = "VIEW")] on your controller, if you want authorize successfully, when you generate the token,you have to add the claim VIEW
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserID",user.Id.ToString()),
//add a new claim named View here
new Claim("View",.......),
new Claim(_options.ClaimsIdentity.RoleClaimType,role.FirstOrDefault())
}),
Trying to restrict access to an endpoint only for users with the role "Manager".
when I open swagger, I use Login endpoint to login(it's successful) and then I try to run 'AddCategory' endpoint and response is 404.
[Authorize(Roles = "Manager")]
[HttpPost("addCategory")]
[SwaggerResponse((int)HttpStatusCode.OK, type: null)]
[SwaggerResponse((int)HttpStatusCode.Unauthorized)]
[SwaggerResponse((int)HttpStatusCode.Forbidden)]
[SwaggerResponse((int)HttpStatusCode.InternalServerError)]
public Category AddCategory([FromBody] Category category)
{
context.Categories.Add(category);
context.SaveChanges();
return category;
}
Same happens if I do not log in. If I remove Authorize attribute, everything works fine.
I checked UserRoles table and user I'm logging in with is Manager. Also tried to put only [Authorize] attribute instead of role specific and still didn't work. In Swagger, Login endpoint returns 200 response but is there any other way to check if I'm logged in or not?
EDIT
This is how I Generate JwtToken on Login.
var roleId = _appDbContext.UserRoles.Where(x => x.UserId == user.Id)
.Select(x=>x.RoleId)
.FirstOrDefault();
var role = _appDbContext.Roles.Where(x=>x.Id==roleId).FirstOrDefault();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Id", user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, role.Name)
}),
Expires = DateTime.UtcNow.AddSeconds(30), // 5-10
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
Decoded token looks like this
"alg": "HS256",
"typ": "JWT"
}.{
"Id": "3f4b1e8f-d15c-426e-9aa6-375ac53f27ae",
"email": "manager#example.com",
"sub": "manager#example.com",
"jti": "6e1fb89e-5408-4c61-8258-8d2c7cead4ab",
"role": "Manager",
"nbf": 1658758635,
"exp": 1658758665,
"iat": 1658758635
}.[Signature]
However, I've never added anything in [AspNetRoleClaims] table. Should I add data to that table too?
EDIT :
This is Program.cs
var key = Encoding.ASCII.GetBytes("llvudfvkwvepwkdnsnwmuulyvtrawppf");
var tokenValidationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
RequireExpirationTime = false,
ClockSkew = TimeSpan.Zero
};
builder.Services.AddSingleton(tokenValidationParams);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwt =>
{
jwt.SaveToken = true;
jwt.TokenValidationParameters = tokenValidationParams;
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Add app.UseAuthentication(); before app.UseAuthorization(); in program.cs file
Context
I have one web application seperated in two "utilities".
Web API
Razor Pages
So my app is both an API and a ASP.NET Razor Pages app, to authenticate on the Web API side I use JWT Bearer and on Web App side a simple Cookie.
Problem
When using Cookie Authentication, I followed Microsoft's Use cookie authentication without ASP.NET Core Identity and it's absolutely not working AT ALL.
I use a custom AuthManager with a SignInAsync method. The cookie is indeed created BUT the ClaimsPrincipal in my HttpContext is empty
I didn't find ANY SOLUTION AT ALL on internet, the only solutions that seemed viable were by using custom Middlewares but I don't even know where to start.
If anybody encountered the same problem as me.
Thanks
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
UserModel user = _userService.UserLogin(email, password).MapFromBLL();
if (user is null) return false;
user.Roles = _roleService.GetUserRoles(user.Id).Select(r => r.MapFromBLL());
if (user.Roles is null || user.Roles.Count() == 0) return false;
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim("Stamp", user.SecurityStamp.ToString())
};
IEnumerable<Claim> roleClaims = user.Roles.Select(ur => new Claim(ClaimTypes.Role, ur.Name));
claims.AddRange(roleClaims);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTime.Now.AddDays(_jwtModel.ExpirationInDays),
IsPersistent = true,
IssuedUtc = DateTime.Now,
};
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
try
{
await _httpAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties);
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return false;
}
}
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.None;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = jwtModel.Issuer,
ValidAudience = jwtModel.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtModel.Secret)),
ClockSkew = TimeSpan.Zero
};
});
EDIT 10-02-2022
I seperated the post in two chapters : "Context" and "Problem"
My solution
Ok, so I found a solution to my problem. It was, as usual after 10 hours of research, dumb. In my Startup.cs I used JwtBearerDefaults.AuthenticationScheme as DefaultAuthenticateScheme. So I changed it with CookieAuthenticationDefaults.AuthenticationScheme, and now by miracle IT WORKS. After logging in, my ClaimsPrincipal User is full and it retrieve correctly the cookie.
My new services.AddAuthentication() in Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
I don't really know if I should keep options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;. If anyone could enlight me.
I didn't change anything in my method public async Task<bool> SignInAsync() I just added await _httpAccessor.HttpContext.SignOutAsync() at the beginning just to be sure.
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
await LogOutAsync();
// The same code as before
}
The LogOutAsync() method
public async Task LogOutAsync()
{
try
{
await _httpAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
Problems encountered
Authorization
After fixing this problem, I had another one. Now that my default scheme is Cookie based, I can't just use [Authorize] and except it to work with both Cookie or Jwt. To fix that I just added this code in Startup.cs after services.AddAuthentication()
AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.DefaultPolicy = multiSchemePolicy;
});
Code that I found here
API Unauthorize return Login Page instead of 401 or 403 status codes
This one is kind of strange, now the cookie and the JwtBearer worked fine, when I tried to access an [Authorize] route, sometimes it returned me the HTML page to Login instead of 401 Status Code and sometimes not.
To avoid this problem, I found a solution here
using services.ConfigureApplicationCookie didn't work for me, instead I adapted my services.AddCookie().
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 403;
}
return Task.CompletedTask;
}
};
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
});
VoilĂ , I hope I have helped at least one of you. If there is anything to change, just let me know !
Complete code
Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = jwtModel.Issuer,
ValidAudience = jwtModel.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtModel.Secret)),
ClockSkew = TimeSpan.Zero
};
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Forbidden";
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 401;
}
return Task.CompletedTask;
},
OnRedirectToAccessDenied = (ctx) =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = 403;
}
return Task.CompletedTask;
}
};
options.EventsType = typeof(SecurityStampUpdatedCookieAuthenticationEvent);
});
AuthorizationPolicy multiSchemePolicy = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.DefaultPolicy = multiSchemePolicy;
});
AuthManager.cs
public async Task<bool> LogInAsync(string email, string password, bool rememberMe)
{
await LogOutAsync();
UserModel user = _userService.UserLogin(email, password).MapFromBLL();
if (user is null) return false;
user.Roles = _roleService.GetUserRoles(user.Id).Select(r => r.MapFromBLL());
if (user.Roles is null || user.Roles.Count() == 0) return false;
List<Claim> claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim("Stamp", user.SecurityStamp.ToString())
};
IEnumerable<Claim> roleClaims = user.Roles.Select(ur => new Claim(ClaimTypes.Role, ur.Name));
claims.AddRange(roleClaims);
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTime.Now.AddDays(_jwtModel.ExpirationInDays),
IsPersistent = true,
IssuedUtc = DateTime.Now,
};
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
try
{
await _httpAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, authProperties);
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return false;
}
}
public async Task LogOutAsync()
{
try
{
await _httpAccessor.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
}
}
How to regenerate open id connect custom claims from asp.net core project?
I've setup manually mapping to claim type name, but some times i need to update other claim from outside of the event OnTicketRecieved, namely from controller, so at that stage i do need to regenerate somehow claims. I've set up openIdConnect in the following way:
_services
.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.Authority = $"{baseAuthorityUrl}/{tenantId}";
options.CallbackPath = new PathString(callBackPath);
options.Scope.Add("email");
options.Scope.Add("profile");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
};
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = e =>
{
return Task.CompletedTask;
},
OnTicketReceived = e =>
{
e.Principal.Identities.First().AddClaim(new Claim(ClaimTypes.Name, e.Principal.FindFirstValue("name")));
return Task.CompletedTask;
}
};
})
How i can to regenerate claims from controller?
I'm thinking just somehow override signInManager.RefreshSignInAsync(user).
If you want to add claims in controller after init login , you should make the authentication manager to use the new identity :
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
identity.AddClaim(new Claim("userId", "1234"));
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(HttpContext.User.Identity));
}
This is how you can update claims outside of the login event. Update is a controller method.
public async Task Update()
{
AuthenticateResult authenticateResult = await HttpContext.AuthenticateAsync();
// Make a copy of the principal so we can modify it's claims
ClaimsPrincipal newPrincipal = new ClaimsPrincipal(User.Identity)
ClaimsIdentity claimsIdentity = (ClaimsIdentity)newPrincipal.Identity;
// Add/remove claims
claimsIdentity.AddClaim(new Claim("name", "value"));
Claim toRemove = claimsIdentity.Claims.FirstOrDefault(c => string.Equals(c.Type, "claimnametoremove", StringComparison.Ordinal));
if (toRemove != null)
claimsIdentity.RemoveClaim(toRemove);
// If these aren't updated, calls to "User" will pull the old principal value
HttpContext.User = newPrincipal;
Thread.CurrentPrincipal = newPrincipal;
// Sign in the user with the new principal to "refresh" our logged-in user
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, newPrincipal, authenticateResult.Properties);
}
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.
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();