Я хочу обновить свои токены доступа и обновления в моей бэкэнде, используя промежуточное программное обеспечение. Когда я отправляю запрос в свой API, я беру жетон из контекста и добавляю его в заголовок авторизации.services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
opt =>
{
opt.SaveToken = true;
opt.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["TokenSettings:Secret"]!)),
ValidateLifetime = true,
ValidIssuer = configuration["TokenSettings:Issuer"],
ValidAudience = configuration["TokenSettings:Audience"],
ClockSkew = TimeSpan.Zero,
NameClaimType = ClaimTypes.Name
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies[configuration["TokenSettings:CookieName"]!];
return Task.CompletedTask;
}
};
});
< /code>
Я хочу реализовать систему токенов обновления, и когда я получу ошибку 401, я хочу получить новый доступ и токены обновления и продолжать отправлять тот же запрос. Вот мой обновленная моментальная программа: < /p>
public sealed class RefreshTokenMiddleware(RequestDelegate Next, IOptions TokenSettings)
{
private readonly RequestDelegate _next = Next;
private readonly TokenSetting _tokenSettings = TokenSettings.Value;
public async Task InvokeAsync(HttpContext context)
{
await _next(context);
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
var refreshToken = context.Request.Cookies[_tokenSettings.RefreshCookieName];
if (refreshToken is not null)
{
var tokenService = context.RequestServices.GetRequiredService();
var tokenResult = await tokenService.GenerateTokenFromRefreshToken(refreshToken);
if (tokenResult.IsSuccess)
{
context.Request.Headers["Authorization"] = $"Bearer {tokenResult.Value}";
await _next(context);
}
}
}
}
}
< /code>
my tokenservice: < /p>
using Application.Services.TokenService;
namespace Infrastructure.Services.TokenService
{
public sealed class TokenService(
IHttpContextAccessor contextAccessor,
UserManager userManager,
IOptions tokenSettings) : ITokenService
{
private readonly HttpContext _context = contextAccessor.HttpContext!;
private readonly UserManager _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
private readonly TokenSetting _tokenSettings = tokenSettings?.Value ?? throw new ArgumentNullException(nameof(tokenSettings));
public async Task GenerateToken(AppUser user, IList roles)
{
if (user == null) return Result.Failure(Error.BadRequest(UserTaskErrors.UserTaskNotFound));
if (roles == null) return Result.Failure(Error.BadRequest(TokenErrors.InvalidRole));
var claims = GetClaims(user, roles);
var token = CreateJwtToken(claims);
var tokenHandler = new JwtSecurityTokenHandler();
var addClaimsResult = await _userManager.AddClaimsAsync(user, claims);
if (!addClaimsResult.Succeeded)
{
return Result.Failure(Error.InvalidRequest(TokenErrors.FailedToAddClaim));
}
string accessToken = tokenHandler.WriteToken(token);
AppendCookie(accessToken);
await GenerateRefreshToken(user);
return Result.Success(accessToken);
}
public async Task GenerateRefreshToken(AppUser user)
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
string uniqueData = $"{user.Id}-{user.Email}-{DateTime.UtcNow.Ticks}";
byte[] uniqueBytes = Encoding.UTF8.GetBytes(uniqueData);
byte[] combinedBytes = randomNumber.Concat(uniqueBytes).ToArray();
using var sha256 = SHA256.Create();
byte[] hashBytes = sha256.ComputeHash(combinedBytes);
string refreshToken = Convert.ToBase64String(hashBytes);
await _userManager.Users.Where(u => u.Id == user.Id)
.ExecuteUpdateAsync(x => x.SetProperty(u => u.RefreshToken, refreshToken));
AppendCookie(refreshToken, true);
return Result.Success(refreshToken);
}
public Result GetPrincipalFromExpiredToken(string token)
{
try
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Secret)),
ValidateLifetime = false
};
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
{
return Result.Failure(Error.InvalidRequest(TokenErrors.InvalidToken));
}
return Result.Success(principal);
}
catch (Exception)
{
return Result.Failure(Error.InvalidRequest(TokenErrors.InvalidToken));
}
}
public async Task GenerateTokenFromRefreshToken(string refreshToken)
{
var user = await ValidateRefreshToken(refreshToken);
if (user == null)
{
return Result.Failure(Error.InvalidRequest(TokenErrors.InvalidRefreshToken));
}
var roles = await _userManager.GetRolesAsync(user);
var result = await GenerateToken(user, roles);
if (result.IsFailure)
{
return Result.Failure(result.Error);
}
return Result.Success(result.Value);
}
private List GetClaims(AppUser user, IList roles)
{
var claims = new List
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("UserId", user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email!),
new Claim(ClaimTypes.Name, user.UserName!)
};
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
return claims;
}
private JwtSecurityToken CreateJwtToken(List claims)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Secret));
return new JwtSecurityToken(
issuer: _tokenSettings.Issuer,
audience: _tokenSettings.Audience,
expires: DateTime.Now.AddDays(_tokenSettings.Expiration),
claims: claims,
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
}
private void AppendCookie(string token, bool isRefresh = false)
{
if (!isRefresh)
{
CookieOptions option = new()
{
HttpOnly = true,
Expires = DateTime.Now.AddSeconds(_tokenSettings.Expiration),
SameSite = SameSiteMode.Strict,
Secure = true
};
_context?.Response.Cookies.Append(_tokenSettings.CookieName, token, option);
return;
}
CookieOptions refreshTokenOption = new()
{
HttpOnly = true,
Expires = DateTime.Now.AddDays(_tokenSettings.ExpirationRefresh),
SameSite = SameSiteMode.Strict,
Secure = true
};
_context?.Response.Cookies.Append(_tokenSettings.RefreshCookieName, token, refreshTokenOption);
}
private async Task ValidateRefreshToken(string refreshToken)
{
return await _userManager.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken);
}
}
}
< /code>
Моя проблема заключается в том, что когда мой токен доступа истекает, и я получаю ошибку 401, я могу создавать и хранить новые токены, но я все еще снова получаю ошибку 401. Мне нужно повторно отправить запрос, чтобы получить 200 ответа.public sealed class TokenService(
IHttpContextAccessor contextAccessor) : ITokenService
{
private readonly IHttpContextAccessor _contextAccessor = contextAccessor;
public async Task CreateAccess()
{
//I create access and refresh token and store them in the cookies of response
_contextAccessor.HttpContext?.Response.Cookies.Append("access_token", "access_token");
await CreateRefresh();
//I return it because I use it in the middleware for sending request but normally I never return it
return "access_token";
}
public Task CreateRefresh()
{
_contextAccessor.HttpContext?.Response.Cookies.Append("refresh_token", "refresh_token");
//Add refresh token to database AppUser refreshToken column
return Task.FromResult("refresh_token");
}
public async Task GenerateTokenFromRefreshToken(string refreshToken)
{
//Check refresh token is correct in database AppUser refreshToken column
// If correct, generate new access token and refresh token
var acc = await CreateAccess();
//when I create the access and refresh they are stored in the cookies of response also
return acc;
}
}
< /code>
program.cs
builder.Services.AddAuthentication().AddJwtBearer("Bearer",
opt =>
{
opt.Events = new JwtBearerEvents
{
// When I get request I take the cookies and put them in the token
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["access_token"];
return Task.CompletedTask;
}
};
});
< /code>
rewreshtokenmiddleware < /h2>
public sealed class RefreshTokenMiddleware(RequestDelegate Next)
{
private readonly RequestDelegate _next = Next;
public async Task InvokeAsync(HttpContext context)
{
await _next(context);
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
var refreshToken = context.Request.Cookies["refresh_token"];
if (refreshToken is not null)
{
var tokenService = context.RequestServices.GetRequiredService();
//store new access and refresh token in the cookies of response
var tokenResult = await tokenService.GenerateTokenFromRefreshToken(refreshToken);
if (string.IsNullOrEmpty(tokenResult))
{
//they works well but the requet return the 401 status code when I send the request again after I got 401 it gives me 200 that means token genereated and stored correctly but my middleware request cant send the request again
context.Request.Headers["Authorization"] = $"Bearer {tokenResult}";
await _next(context);
}
}
}
}
}
Подробнее здесь: https://stackoverflow.com/questions/789 ... net-core-8