Сообщение об ошибке
IDX10223: Lifetime validation failed. The token is expired.
Также выводится как
- Bearer error="invalid_token", error_description="The token expired at '10/20/2026 06:28:30'"
- IDX10222: Lifetime validation failed. The token is not yet valid.
Текст после «The token is expired.» содержит обе метки времени в формате региональных параметров (culture) сервера. Описание в `WWW-Authenticate` в ASP.NET Core формирует `JwtBearerHandler`.
Microsoft.IdentityModel.Tokens (.NET) — SecurityTokenExpiredException
Что это означает
Значение `exp` в токене раньше текущего времени минус `ClockSkew` (по умолчанию 5 минут). Подпись и остальные утверждения могут быть в порядке: просто срок жизни токена истёк. Дальше сообщение продолжается так: «ValidTo (UTC): …, Current time (UTC): …», а ASP.NET Core сообщает клиенту `error_description="The token expired at '…'"`. IDX10222 — противоположный случай: `nbf` находится в будущем.
Почему это происходит
Срок жизни токена истёк
частоТокены доступа Entra ID по умолчанию живут 60–90 минут, у других поставщиков часто 5–15 минут. Клиент, который продолжает использовать тот же токен после `exp`, получает ошибку точно по расписанию.
Расхождение часов больше допустимого
иногдаПри уменьшенном `ClockSkew` (например, `TimeSpan.Zero`, который часто копируют из примеров) даже расхождение в несколько секунд между часами издателя и API приводит к отклонению свежих токенов.
Токен закеширован в обход библиотеки токенов
иногдаТокены, сохранённые в базе данных, статическом поле или файле конфигурации, никогда не обновляются. MSAL и подобные библиотеки обновляют их автоматически, если им это позволить.
Как исправить
- 1.
Оставьте небольшой допуск на расхождение часов
Допускайте расхождение в секунды, а не в минуты, и исправьте часы. Никогда не задавайте `ValidateLifetime = false` в рабочей среде.
csharp options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = "https://login.example.com/", ValidAudience = "api://orders", ClockSkew = TimeSpan.FromSeconds(30), // default is 5 minutes; lower it, do not raise it to hide expiry }; - 2.
Получайте токены через библиотеку при каждом вызове
`AcquireTokenSilent` возвращает действительный токен из кеша или обновляет его, поэтому истечение срока действия обрабатывается ещё до запроса.
csharp // Client side (MSAL): ask for a token every time; MSAL returns the cached one or refreshes it. var result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(); httpClient.DefaultRequestHeaders.Authorization = new("Bearer", result.AccessToken);