admin管理员组文章数量:1390399
I'm working on JWT authentication/authorization project, following this guide.
I have successfully wired up the JWT infrastructure and I'm able to login successfully and generate the required tokens.
Additionally, I'm using AddIdentityCore
, so I have wired up the required service from the source code.
services.AddIdentityCore<User>()
.AddRoles<UserRole>()
.AddEntityFrameworkStores<NesnasWorkerEfContext>()
//.AddClaimsPrincipalFactory<UsersClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
services.TryAddScoped<IUserValidator<User>, UserValidator<User>>();
services.TryAddScoped<IPasswordValidator<User>, PasswordValidator<User>>();
services.TryAddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<UserRole>, RoleValidator<UserRole>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<User>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<User>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<User>, UserClaimsPrincipalFactory<User, UserRole>>();
services.TryAddScoped<IUserConfirmation<User>, DefaultUserConfirmation<User>>();
services.TryAddScoped<UserManager<User>>();
services.TryAddScoped<SignInManager<User>>();
services.TryAddScoped<RoleManager<UserRole>>();
The first issue comes into place when I move forward with the actions, for example Logout
endpoint doesn't recognize that the user is authenticated and the ClaimsPrincipal
is unauthenticated and shows IsAuthenticated = false
public static async Task<Results<Ok, UnauthorizedHttpResult>> Logout(
SignInManager<User> signInManager,
IJwtProvider jwtProvider,
ClaimsPrincipal principal)
{
if (!principal.Identity!.IsAuthenticated)
{
return TypedResults.Unauthorized();
}
await signInManager.SignOutAsync();
await jwtProvider.RevokeToken(principal);
return TypedResults.Ok();
}
However, I was able to workaround this issue by using
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme
instead of
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme
in the services.AddAuthentication
section:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
//options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; <-- This doesn't work
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = tokenValidationParameters;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidateAsync<ITwoFactorSecurityStampValidator>
};
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
Now, after doing the above change, I noticed is that there are 2 identities when I inspect the ClaimsPrincipal
, one that is being authenticated and the other is not. However, from my understanding, there should be a single identity that is pointing up to the authenticated user.
Furthermore, by investigating the principal, I found that the one that is authenticated have the default Claims
, such as issuer LOCAL AUTHORITY
. Although, mine is being setup in TokenValidationParameters
is different and I've setup custom claims while generating the token, which is not showing up.
public async Task<Result<AccessTokenResponse>> GenerateToken(ClaimsPrincipal principal)
{
var claims = new List<Claim>();
var userId = principal.GetUserId();
var user = await userRepository.GetByIdAsync(userId);
claims.Add(new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
claims.Add(new(JwtRegisteredClaimNames.Sub, userId.ToString()));
claims.Add(new(JwtRegisteredClaimNames.Email, principal.GetUserEmail()));
claims.Add(new(ApplicationClaimTypes.Id, userId.ToString()));
claims.Add(new(ApplicationClaimTypes.TenantId, user!.TenantId.ToString()!));
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken(new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(claims),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenOptions.Secret)),
SecurityAlgorithms.HmacSha256Signature),
Issuer = _tokenOptions.ValidIssuer,
Audience = _tokenOptions.ValidAudience,
Expires = DateTime.UtcNow.AddSeconds(_tokenOptions.AccessTokenLifeTime),
});
var refreshToken = new UserRefreshToken(
token.Id,
DateTime.UtcNow,
DateTime.UtcNow.AddDays(_tokenOptions.RefreshTokenLifeTime),
userId);
await refreshTokenRepository.CreateRefreshTokenAsync(refreshToken);
await context.SaveChangesAsync();
return Result.Success(new AccessTokenResponse
{
AccessToken = handler.WriteToken(token),
RefreshToken = refreshToken.Token.ToString(),
ExpiresIn = _tokenOptions.AccessTokenLifeTime * 60,
});
}
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = configuration["Jwt:ValidIssuer"],
ValidateAudience = true,
ValidAudience = configuration["Jwt:ValidAudience"],
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Secret"]!)),
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero
};
Lastly, I've tried to create my own UserClaimsPrincipalFactory
and pass in the custom claims. However, this created 3 identities, the default one IsAuthenticated = true
, another identity with the custom claims passed in the factory as IsAuthenticated = false
and the last one with IsAuthenticated = false
.
public class UsersClaimsPrincipalFactory(
UserManager<User> userManager,
RoleManager<UserRole> roleManager,
IOptions<IdentityOptions> optionsAccessor)
: UserClaimsPrincipalFactory<User, UserRole>(userManager, roleManager, optionsAccessor)
{
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var principal = await base.CreateAsync(user);
var claims = new List<Claim>();
var userId = principal.GetUserId();
claims.Add(new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
claims.Add(new(JwtRegisteredClaimNames.Sub, userId.ToString()));
claims.Add(new(JwtRegisteredClaimNames.Email, principal.GetUserEmail()));
claims.Add(new(ApplicationClaimTypes.Id, userId.ToString()));
claims.Add(new(ApplicationClaimTypes.TenantId, user!.TenantId.ToString()!));
principal.AddIdentity(new ClaimsIdentity(claims));
return principal;
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user)
{
ClaimsIdentity claims = await base.GenerateClaimsAsync(user);
return claims;
}
}
Here is my login endpoint:
public static async Task<Results<Ok<AccessTokenResponse>, UnauthorizedHttpResult>> Login(
HttpContext context,
LoginRequest request,
SignInManager<User> signInManager,
UserManager<User> userManager,
IJwtProvider jwtProvider)
{
var user = await userManager.FindByEmailAsync(request.Email);
if (user is null)
{
return TypedResults.Unauthorized();
}
var result = await signInManager.PasswordSignInAsync(
user.UserName!,
request.Password,
isPersistent: false,
lockoutOnFailure: false);
if (!result.Succeeded)
{
return TypedResults.Unauthorized();
}
var tokenResult = await jwtProvider.GenerateToken(context.User);
if (tokenResult.IsFailure)
{
return TypedResults.Unauthorized();
}
return TypedResults.Ok(tokenResult.Value);
}
I'd appreciate your support in resolving the issue of having multiple identities and IsAuthenticated = false
.
I'm working on JWT authentication/authorization project, following this guide.
I have successfully wired up the JWT infrastructure and I'm able to login successfully and generate the required tokens.
Additionally, I'm using AddIdentityCore
, so I have wired up the required service from the source code.
services.AddIdentityCore<User>()
.AddRoles<UserRole>()
.AddEntityFrameworkStores<NesnasWorkerEfContext>()
//.AddClaimsPrincipalFactory<UsersClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
services.TryAddScoped<IUserValidator<User>, UserValidator<User>>();
services.TryAddScoped<IPasswordValidator<User>, PasswordValidator<User>>();
services.TryAddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<UserRole>, RoleValidator<UserRole>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<User>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<User>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<User>, UserClaimsPrincipalFactory<User, UserRole>>();
services.TryAddScoped<IUserConfirmation<User>, DefaultUserConfirmation<User>>();
services.TryAddScoped<UserManager<User>>();
services.TryAddScoped<SignInManager<User>>();
services.TryAddScoped<RoleManager<UserRole>>();
The first issue comes into place when I move forward with the actions, for example Logout
endpoint doesn't recognize that the user is authenticated and the ClaimsPrincipal
is unauthenticated and shows IsAuthenticated = false
public static async Task<Results<Ok, UnauthorizedHttpResult>> Logout(
SignInManager<User> signInManager,
IJwtProvider jwtProvider,
ClaimsPrincipal principal)
{
if (!principal.Identity!.IsAuthenticated)
{
return TypedResults.Unauthorized();
}
await signInManager.SignOutAsync();
await jwtProvider.RevokeToken(principal);
return TypedResults.Ok();
}
However, I was able to workaround this issue by using
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme
instead of
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme
in the services.AddAuthentication
section:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
//options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; <-- This doesn't work
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = tokenValidationParameters;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidateAsync<ITwoFactorSecurityStampValidator>
};
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
Now, after doing the above change, I noticed is that there are 2 identities when I inspect the ClaimsPrincipal
, one that is being authenticated and the other is not. However, from my understanding, there should be a single identity that is pointing up to the authenticated user.
Furthermore, by investigating the principal, I found that the one that is authenticated have the default Claims
, such as issuer LOCAL AUTHORITY
. Although, mine is being setup in TokenValidationParameters
is different and I've setup custom claims while generating the token, which is not showing up.
public async Task<Result<AccessTokenResponse>> GenerateToken(ClaimsPrincipal principal)
{
var claims = new List<Claim>();
var userId = principal.GetUserId();
var user = await userRepository.GetByIdAsync(userId);
claims.Add(new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
claims.Add(new(JwtRegisteredClaimNames.Sub, userId.ToString()));
claims.Add(new(JwtRegisteredClaimNames.Email, principal.GetUserEmail()));
claims.Add(new(ApplicationClaimTypes.Id, userId.ToString()));
claims.Add(new(ApplicationClaimTypes.TenantId, user!.TenantId.ToString()!));
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken(new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(claims),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenOptions.Secret)),
SecurityAlgorithms.HmacSha256Signature),
Issuer = _tokenOptions.ValidIssuer,
Audience = _tokenOptions.ValidAudience,
Expires = DateTime.UtcNow.AddSeconds(_tokenOptions.AccessTokenLifeTime),
});
var refreshToken = new UserRefreshToken(
token.Id,
DateTime.UtcNow,
DateTime.UtcNow.AddDays(_tokenOptions.RefreshTokenLifeTime),
userId);
await refreshTokenRepository.CreateRefreshTokenAsync(refreshToken);
await context.SaveChangesAsync();
return Result.Success(new AccessTokenResponse
{
AccessToken = handler.WriteToken(token),
RefreshToken = refreshToken.Token.ToString(),
ExpiresIn = _tokenOptions.AccessTokenLifeTime * 60,
});
}
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = configuration["Jwt:ValidIssuer"],
ValidateAudience = true,
ValidAudience = configuration["Jwt:ValidAudience"],
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Secret"]!)),
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero
};
Lastly, I've tried to create my own UserClaimsPrincipalFactory
and pass in the custom claims. However, this created 3 identities, the default one IsAuthenticated = true
, another identity with the custom claims passed in the factory as IsAuthenticated = false
and the last one with IsAuthenticated = false
.
public class UsersClaimsPrincipalFactory(
UserManager<User> userManager,
RoleManager<UserRole> roleManager,
IOptions<IdentityOptions> optionsAccessor)
: UserClaimsPrincipalFactory<User, UserRole>(userManager, roleManager, optionsAccessor)
{
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var principal = await base.CreateAsync(user);
var claims = new List<Claim>();
var userId = principal.GetUserId();
claims.Add(new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
claims.Add(new(JwtRegisteredClaimNames.Sub, userId.ToString()));
claims.Add(new(JwtRegisteredClaimNames.Email, principal.GetUserEmail()));
claims.Add(new(ApplicationClaimTypes.Id, userId.ToString()));
claims.Add(new(ApplicationClaimTypes.TenantId, user!.TenantId.ToString()!));
principal.AddIdentity(new ClaimsIdentity(claims));
return principal;
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user)
{
ClaimsIdentity claims = await base.GenerateClaimsAsync(user);
return claims;
}
}
Here is my login endpoint:
public static async Task<Results<Ok<AccessTokenResponse>, UnauthorizedHttpResult>> Login(
HttpContext context,
LoginRequest request,
SignInManager<User> signInManager,
UserManager<User> userManager,
IJwtProvider jwtProvider)
{
var user = await userManager.FindByEmailAsync(request.Email);
if (user is null)
{
return TypedResults.Unauthorized();
}
var result = await signInManager.PasswordSignInAsync(
user.UserName!,
request.Password,
isPersistent: false,
lockoutOnFailure: false);
if (!result.Succeeded)
{
return TypedResults.Unauthorized();
}
var tokenResult = await jwtProvider.GenerateToken(context.User);
if (tokenResult.IsFailure)
{
return TypedResults.Unauthorized();
}
return TypedResults.Ok(tokenResult.Value);
}
I'd appreciate your support in resolving the issue of having multiple identities and IsAuthenticated = false
.
1 Answer
Reset to default 0For 8 or higher versions, you could protect your webapi project with Identity framework follow the official document
Register Identity Api service and Authorization service:
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthorization();
Map Identity Endpoints route;
app.MapIdentityApi<IdentityUser>();
本文标签: cMultiple identities in ClaimsPrincipal with IsAuthenticatedfalse in JWTStack Overflow
版权声明:本文标题:c# - Multiple identities in ClaimsPrincipal with IsAuthenticated = false in JWT - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744671691a2618863.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论