Jak zaimplementować refresh tokenu JWT?

0

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

Ktoś? Coś?

Coś pokombinowałem i teraz jak używam refresh tokena to aplikacja przerywa działanie i dostaję taki komunikat jak poniżej.

"The program '[7500] ...' has exited with code 3221225477 (0xc0000005) 'Access violation'."

1 użytkowników online, w tym zalogowanych: 0, gości: 1