Какой статус-код HTTP следует возвращать, если срок действия токена (access token) истек?

Ответ

Согласно спецификации RFC 6750 (Bearer Token Usage) и общепринятой практике, для просроченного (или недействительного) токена должен возвращаться статус 401 Unauthorized.

Почему именно 401, а не 403?

  • 401 Unauthorized означает, что запрос не содержит корректных учетных данных для аутентификации. Сервер "не знает", кто вы. Просроченный токен — это некорректные учетные данные.
  • 403 Forbidden означает, что сервер понял, кто вы (аутентификация успешна), но у вас недостаточно прав для доступа к ресурсу. Это другая ситуация.

Правильная реализация в ASP.NET Core:

  1. В обработчике аутентификации (например, JWT Bearer): При проверке токена, если он просрочен, схема аутентификации автоматически завершится с ошибкой, и фреймворк вернет 401.

  2. Для явного возврата с дополнительной информацией (в контроллере или middleware):

    [Authorize]
    public class SecureController : ControllerBase
    {
        [HttpGet("data")]
        public IActionResult GetData()
        {
            // Проверка, выполненная кастомной логикой
            if (HttpContext.User.IsTokenExpired())
            {
                // Рекомендуется также добавить заголовок WWW-Authenticate
                Response.Headers.WWWAuthenticate = "Bearer error="invalid_token", error_description="The access token expired"";
                return Unauthorized(new { error = "invalid_token", message = "Token has expired" });
            }
            return Ok("Sensitive data");
        }
    }
  3. Использование [Authorize] с политиками: Можно создать политику, которая проверяет не только наличие, но и валидность/срок токена.

    services.AddAuthorization(options =>
    {
        options.AddPolicy("ValidToken", policy =>
            policy.RequireAssertion(context =>
                context.User.Identity?.IsAuthenticated == true && 
                !context.User.IsTokenExpired()));
    });

    Затем использовать [Authorize(Policy = "ValidToken")]. При неудаче политики также вернется 401.

Ключевой вывод: Всегда используйте 401 Unauthorized для проблем с аутентификацией (неверный/просроченный/отозванный токен). Используйте 403 Forbidden только когда пользователь аутентифицирован, но его роли или claims не позволяют выполнить действие.

Ответ 18+ 🔞

А, слушай, вот этот ваш RFC 6750 — он, конечно, документ, но там всё по полочкам разложено, как в аптеке. Суть-то проще пареной репы.

Представь, ты приходишь в закрытый клуб, суёшь охраннику свою карточку, а он смотрит и такой: «Мужик, у тебя членство вчера кончилось, просрочено, нахуй». Это 401 Unauthorized. Он тебя не узнаёт, твоя карточка — хуйня, а не удостоверение. Ты для системы — никто, пустое место.

А вот 403 Forbidden — это когда ты уже внутри, с валидной картой, пролез в VIP-зону, но пытаешься зайти в служебный сортир, куда пускают только уборщиц с блядским уровнем доступа. Тебя узнали, но доступ не дали — иди нахуй, не положено.

Так вот, просроченный токен — это однозначно первый случай. Ты не авторизован, точка. Возвращаем 401, и всё тут.

Теперь смотри, как это в коде выглядит, без этих ваших заумных терминов.

В основном, если ты используешь стандартную аутентификацию через AddJwtBearer, то за тебя всё уже сделали. Токен просрочился — схема аутентификации фейлится, и фреймворк автоматом отправит 401. Красота.

Но если ты хочешь добавить немного информативности, чтобы фронтенд-ребята не гадали, что случилось, можно и ручками потыкать. Например, в контроллере:

[Authorize] // Это чтобы просто так любой не лез
public class MySecretController : ControllerBase
{
    [HttpGet("my-data")]
    public IActionResult GetMyData()
    {
        // Допустим, у тебя есть свой метод проверки
        if (User.IsTokenExpiredLikeADog())
        {
            // Канонично ещё вот этот заголовок добавить, по RFC
            Response.Headers.WWWAuthenticate = "Bearer error="invalid_token", error_description="The token expired, man"";
            // И отдать 401 с пояснением
            return Unauthorized(new { error = "invalid_token", message = "Токен сдох, обнови, брат" });
        }
        return Ok("Вот тебе секретные данные, держи.");
    }
}

Можно вообще заморочиться и сделать свою политику для [Authorize]. Типа так:

services.AddAuthorization(options =>
{
    options.AddPolicy("NotExpiredToken", policy =>
        policy.RequireAssertion(context =>
            context.User.Identity?.IsAuthenticated == true &&
            !context.User.IsTokenExpired() // Твоя кастомная проверка
        ));
});

И потом вешать на метод [Authorize(Policy = "NotExpiredToken")]. Не прошёл проверку по политике — опять же, получишь 401.

Итог, коротко и на пальцах:

  • Токен просрочен, невалидный, отозванный, его вообще нет?401 Unauthorized. «Я тебя не знаю, иди нахуй».
  • Токен есть, он живой, всё ок, но у тебя нет прав на этот конкретный метод/ресурс?403 Forbidden. «Я тебя знаю, но ты сюда не лезь, у тебя прав нет».

Вот и вся философия. Не усложняй, блядь.