Czołem
Mam problem. Mianowicie chcę użyć dwóch schematów uwierzytelnienia z wykorzystaniem tokenów JWT.
Jedna polityka typu Bearer jest zdefiniowana jako domyślna polityka autoryzacji.
Oraz druga jako Refresh. Problem polega na tym gdy uderzam do jakiegokolwiek endpointa, który posiada taki atrybut
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
to wszystko gra i buczy.
Jak chcę wykorzystać politykę, która ma zdefiniowany schemat AuthenticationSchemes.Refresh
to dostaję 401 unathorized.
Logi rzucają natomiast takim komunikatem:
Authorization failed. These requirements were not met: DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
Jak sobie dekoduję obydwa tokeny to sekcja payload wygląda w nich tak...
{
"jti": "aacbe949-0958-4798-ae38-952c6d51d13f",
"uId": "6760da83-3d05-4049-80a0-0cadedb5a50f",
"aud": "access",
"iInn": "True",
"role": "Administrator",
"rId": "8d984fb4-2441-4a89-9db7-ef8c976b4039",
"nbf": 1702673351,
"exp": 1702673951,
"iat": 1702673351
}
{
"jti": "9e2f16d5-f505-43d0-b780-392d3f673920",
"uId": "6760da83-3d05-4049-80a0-0cadedb5a50f",
"aud": "refresh",
"iInn": "True",
"role": "Refresher",
"nbf": 1702672606,
"exp": 1702679806,
"iat": 1702672606
}
Totalnie nie wiem o co tu kaman. Może ktoś się pochylić nad tym problemem i podpowiedzieć co tu trzeba poprawić aby grało i buczało?
Poniżej szczegóły implementacji.
Rejestracja schematów uwierzytelnienia.
JwtSettings jwtSettings = new();
configuration.Bind(nameof(JwtSettings), jwtSettings);
services.AddSingleton(jwtSettings);
var tokensValidationParameters =
new TokensValidationParameters(
GenerateTokenValidationParameters(jwtSettings.AccessSecret, jwtSettings.AccessAudience, false),
GenerateTokenValidationParameters(jwtSettings.RefreshSecret, jwtSettings.RefreshAudience, false),
GenerateTokenValidationParameters(jwtSettings.TerytImportSecret, jwtSettings.TerytImportAudience, false));
services.AddSingleton(tokensValidationParameters);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x =>
{
tokensValidationParameters.AccessTokenValidationParameters.ValidateLifetime = true;
x.SaveToken = true;
x.TokenValidationParameters = tokensValidationParameters.AccessTokenValidationParameters;
})
.AddJwtBearer(AuthenticationSchemes.Refresh, x =>
{
tokensValidationParameters.RefreshTokenValidationParameters.ValidateLifetime = true;
x.SaveToken = true;
x.TokenValidationParameters = tokensValidationParameters.RefreshTokenValidationParameters;
});
public static TokenValidationParameters GenerateTokenValidationParameters(string secret, string audience, bool validateLifetime)
{
return new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)),
ValidateAudience = true,
ValidAudience = audience,
ValidateIssuer = false,
RequireExpirationTime = true,
ValidateLifetime = validateLifetime,
ClockSkew = TimeSpan.Zero
};
}
public class TokensValidationParameters
{
public TokensValidationParameters(TokenValidationParameters accessTokenValidationParameters, TokenValidationParameters refreshTokenValidationParameters, TokenValidationParameters terytTokenValidationParameters)
{
AccessTokenValidationParameters = accessTokenValidationParameters;
RefreshTokenValidationParameters = refreshTokenValidationParameters;
TerytTokenValidationParameters = terytTokenValidationParameters;
}
public TokenValidationParameters AccessTokenValidationParameters { get; init; }
public TokenValidationParameters RefreshTokenValidationParameters { get; init; }
public TokenValidationParameters TerytTokenValidationParameters { get; init; }
}
Rejestracja polityk.
services.AddAuthorization(a =>
{
var policyBuilder = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme);
a.DefaultPolicy = policyBuilder.RequireAuthenticatedUser().RequireClaim(ClaimTypes.Role).Build();
var onlyRefreshSchemePolicyBuilder = new AuthorizationPolicyBuilder(AuthenticationSchemes.Refresh);
a.AddPolicy(AuthenticationSchemes.Refresh, onlyRefreshSchemePolicyBuilder.RequireAuthenticatedUser().Build());
});
Tworzenie tokenów
public TokenDto CreateToken(BaseId userId, bool isRefresh, bool isInnerUser, Guid? refreshTokenId = null, IEnumerable<RoleVo> roles = null, BaseId userContextId = null, BaseId cityProfileId = null)
{
var key = Encoding.UTF8.GetBytes(isRefresh ? _jwtSettings.RefreshSecret : _jwtSettings.AccessSecret);
var tokenId = Guid.NewGuid();
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, tokenId.ToString()),
new Claim(_claimsNamesSettings.UserIdClaimName, userId),
new Claim(JwtRegisteredClaimNames.Aud, isRefresh ? _jwtSettings.RefreshAudience : _jwtSettings.AccessAudience),
new Claim(_claimsNamesSettings.IsInnerUserClaimName, isInnerUser.ToString())
};
if (!isRefresh)
{
if (refreshTokenId.IsNullOrEmpty())
throw new ArgumentNullException(nameof(refreshTokenId));
if (!isInnerUser)
{
if (userContextId is null || userContextId.Value.IsNullOrEmpty())
throw new ArgumentNullException(nameof(userContextId));
if (cityProfileId is null || cityProfileId.Value.IsNullOrEmpty())
throw new ArgumentNullException(nameof(cityProfileId));
claims.Add(new Claim(_claimsNamesSettings.UserContextIdClaimName, userContextId ?? string.Empty));
claims.Add(new Claim(_claimsNamesSettings.CityProfileIdClaimName, cityProfileId ?? string.Empty));
}
if (roles is null || !roles.Any())
claims.Add(new Claim(_claimsNamesSettings.RoleClaimName, JsonSerializer.Serialize(new List<object>())));
else
foreach (var role in roles)
claims.Add(new Claim(ClaimTypes.Role, role));
claims.Add(new Claim(_claimsNamesSettings.RefreshTokenIdClaimName, refreshTokenId.ToString()));
}
if (isRefresh)
claims.Add(new Claim(ClaimTypes.Role, RoleVo.CreateRefresherRole()));
DateTime expirationDate = DateTime.UtcNow.Add(isRefresh ? _jwtSettings.RefreshTokenLifeTime : _jwtSettings.AccessTokenLifeTime);
var tokenDescriptor = new SecurityTokenDescriptor
{
NotBefore = DateTime.UtcNow,
Subject = new ClaimsIdentity(claims),
Expires = expirationDate,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256)
};
var token = _tokenHandler.CreateToken(tokenDescriptor);
var result = new TokenDto
{
TokenId = tokenId,
UserId = userId,
UserContextId = userContextId ?? null,
Expires = expirationDate,
Token = _tokenHandler.WriteToken(token)
};
_httpContextAccessor.HttpContext?.Items.TryAdd(isRefresh ? _tokenKeysSettings.RefreshTokenKeyName : _tokenKeysSettings.AccessTokenKeyName, result.Token);
return result;
}
Endpointy, które zwracają tokeny access i refresh.
[HttpPost("User/LogIn")]
public async Task<IActionResult> AuthenticateUser([FromBody] AuthenticateUser command)
=> await SendJwtCommandAsync(command);
[HttpPost("User/Refresh")]
[Authorize(Policy = Common.Jwt.AuthenticationSchemes.Refresh)]
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenCommand command)
=> await SendJwtCommandAsync(command);