Как сделать редирект на refresh-token когда получаю AuthenticationFailed на asp.net core web api?
Здравствуйте
Уже настроена аутентификация на проекте asp.net core web api. Нужно сделать, что бы при истечении срока жизни access token делался redirect на refresh-token endpoint, но не могу понять как его вообще сделать. Вот часть конфигурации аутентификации:
.AddJwtBearer(options => { string securityKey = builder.Configuration.GetSection("JwtOptions:SecurityKey").Value; byte[] byteKey = Encoding.UTF8.GetBytes(securityKey); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(byteKey) }; options.Events = new JwtBearerEvents() { OnMessageReceived = context => { context.Token = context.Request.Cookies["access-token"]; return Task.CompletedTask; }, OnAuthenticationFailed = context => { context.Response.Redirect("https://localhost:8876/api/v1.0/authentication/refresh-token"); return Task.CompletedTask; }, OnChallenge = context => // Это не нужно, но так тоже не работает { context.Response.Redirect("https://localhost:8876/api/v1.0/authentication/refresh-token"); return Task.CompletedTask; } }; }); |
.AddJwtBearer(options => { string securityKey = builder.Configuration.GetSection("JwtOptions:SecurityKey").Value; byte[] byteKey = Encoding.UTF8.GetBytes(securityKey); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(byteKey) }; options.Events = new JwtBearerEvents() { OnMessageReceived = context => { context.Token = context.Request.Cookies["access-token"]; return Task.CompletedTask; }, OnAuthenticationFailed = context => { context.Response.Redirect("https://localhost:8876/api/v1.0/authentication/refresh-token"); return Task.CompletedTask; }, OnChallenge = context => // Это не нужно, но так тоже не работает { context.Response.Redirect("https://localhost:8876/api/v1.0/authentication/refresh-token"); return Task.CompletedTask; } }; });
Когда получаю HttpStatusCode401, срабатывает событие OnAuthenticationFailed, но context.Response.Redirect() ничего не делает (Это не странно, он возвращает void). Тут вопрос, а как тогда сделать редирект на другой эндпоинт, что бы получить новый access token перед тем, как вернётся HttpStatusCode401 и вернуть нормальный StatusCode?
Дополнительно:
Привет, почитал ответ и переписку к нему. Можно сделать так, что после успешной авторизации клиента, от сервера в ответе передается сразу два токена, токен доступа и токен обновления. Токен обновления имеет более длительный срок валидности чем токен доступа. И когда клиент пытается в запросе передать истекший токен доступа и еще валидный токен обновления, в этой ситуации ему, на основании валидного токена обновления передается вновь созданный токен доступа. Если будут похищены оба токена, то есть специальный механизм отслеживания такой ситуации на сервере. Однако на клиенте нужно будет настроить обработку ситуации когда оба токена (доступа и обновления) уже не валидны, тогда в логике на клиенте нужно будет предусмотреть автоматический переход к повторной авторизации....Коротко об этой схеме говорится вот в этом видео.
Решил. Как это всё делается? Внутрь onAuthenticationFailed вставляем этот код:
if (context.Exception?.GetType() == typeof(SecurityTokenExpiredException)) { IJwtService jwtService = context.HttpContext.RequestServices.GetService<IJwtService>(); if (context.Request.Cookies.TryGetValue("refresh-token", out string refreshToken)) { Result<ClaimsPrincipal> principalResult = jwtService.ValidateAndDecodeToken(refreshToken); if (!principalResult.IsSuccess || principalResult.Value == null) { return Task.CompletedTask; } Result<string> renewAccessTokenResult = jwtService.Encode(principalResult.Value); if (!renewAccessTokenResult.IsSuccess && renewAccessTokenResult.ValidationErrors.Any()) { return Task.CompletedTask; } context.Response.Cookies.Append("access-token", renewAccessTokenResult.Value); context.Response.StatusCode = 200; context.HttpContext.User = principalResult.Value; context.Principal = principalResult.Value; context.Success(); } } return Task.CompletedTask; |
if (context.Exception?.GetType() == typeof(SecurityTokenExpiredException)) { IJwtService jwtService = context.HttpContext.RequestServices.GetService<IJwtService>(); if (context.Request.Cookies.TryGetValue("refresh-token", out string refreshToken)) { Result<ClaimsPrincipal> principalResult = jwtService.ValidateAndDecodeToken(refreshToken); if (!principalResult.IsSuccess || principalResult.Value == null) { return Task.CompletedTask; } Result<string> renewAccessTokenResult = jwtService.Encode(principalResult.Value); if (!renewAccessTokenResult.IsSuccess && renewAccessTokenResult.ValidationErrors.Any()) { return Task.CompletedTask; } context.Response.Cookies.Append("access-token", renewAccessTokenResult.Value); context.Response.StatusCode = 200; context.HttpContext.User = principalResult.Value; context.Principal = principalResult.Value; context.Success(); } } return Task.CompletedTask;
Т.е.. вызывает сервис, берём refresh token и из него извлекаем ClaimPrincipal, далее на его основе делаем access token из того же сервиса и обрабатываем запрос, что бы он возвращал нужный статус. Как говорил Andrei SunnyPh , это допустимая практика (Спасибо)
Ответы:
Клиент сам должен следить за временем жизни токена и сам делать рефреш.
- Да, но задача стоит сделать это на сервере
- Nik Faraday, это полный бред. В запросе может и не быть refresh token'a (в 99.9% запросах его и не будет, потому что в обычных запросах он и не нужен), который нужен для обновления jwt, тот, кто вам такое ТЗ дал - пусть и скажет вам, как это должно выглядеть. Такие вещи лежат на плечах клиента, но никак не сервера.
- OwDafuq, бред или не бред это другое дело. Возмущаться всегда можно. Можно ещё прийти и сказать, что это всё бред, я сам себе тут начальник, и вы никто ничего не знаете, я знаю лучше всех. Есть задача, ты либо делаешь, либо идёшь на все 4 стороны, найдут того, кто сделает
- Nik Faraday, Как вы это представляете? Вы делаете запрос к точке API, ожидаете определенный ответ, например, список сущностей, а вам прилетает новый токен?
Токен у вас хранит клиент и он сам должен его обновлять. Я в angular приложухе делал интерцептор (перехватчик запросов), который автоматом перехватывал ответ, и если он был 401, обновлял токен и повторял запрос. - Nik Faraday, Если вам дают такие задачи - бегите, не пожалеете.
- Роман, А еще можно с апи отдавать время жизни токена и проверять время жизни без запроса на сервер, проверил время жизни - подходит к концу или кончилось - обновил - послал запрос дальше, но уже с новым токеном
- Роман, это всё зависит от логики приложения. Логика данного приложения позволяет, даже заставляет делать такие вещи
- Nik Faraday, нашел на SO, попробуйте
app.UseStatusCodePages(async context => { var request = context.HttpContext.Request; var response = context.HttpContext.Response; if (response.StatusCode == (int)HttpStatusCode.Unauthorized) // you may also check requests path to do this only for specific methods // && request.Path.Value.StartsWith("/specificPath") { response.Redirect("/account/login") } });
app.UseStatusCodePages(async context => { var request = context.HttpContext.Request; var response = context.HttpContext.Response; if (response.StatusCode == (int)HttpStatusCode.Unauthorized) // you may also check requests path to do this only for specific methods // && request.Path.Value.StartsWith("/specificPath") { response.Redirect("/account/login") } });
Вопрос в другом, где вы refresh-token возьмете? или оба передавать будете?
- Роман, так не работает, я это указал в коде вопроса
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос
Для реализации редиректа на refresh token при получении AuthenticationFailed на ASP.NET Core Web API, вам необходимо использовать Middleware для обработки исключений и выполнения нужных действий.
Прежде всего, вам необходимо создать Middleware, который будет перехватывать исключения и выполнять редирект на refresh token. Для этого вам нужно создать класс, который будет обрабатывать исключения:
```php
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (AuthenticationFailedException ex)
{
// Перенаправляем на refresh token
context.Response.Redirect("/refresh-token");
}
}
}
```
Затем зарегистрируйте Middleware в классе Startup.cs:
```php
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware();
}
```
Теперь, когда возникает исключение AuthenticationFailed, пользователь будет автоматически перенаправлен на страницу refresh token. Не забудьте реализовать логику обновления токена на этой странице.
Надеюсь, это поможет вам решить проблему с редиректом на refresh token при получении AuthenticationFailed на ASP.NET Core Web API. Если у вас возникнут дополнительные вопросы, не стесняйтесь задавать.