Автоматическое обновление аутентифицируется при предварительном просмотре OPTIONS, но не в GET для конечной точки UserInfo
Среда:
- Экземпляр https://identityserver.io/, поддерживающий неявный поток
- Клиентские приложения Angular 7, использующие oidc-client-js
- Ресурсы веб-API ASP.NET Framework с использованием промежуточного программного обеспечения для проверки маркеров доступа IdentityServer3 Katana
Базовая выдача и проверка токенов работает нормально в нашей среде. Сейчас я пытаюсь включить технику тихого обновления (как описано здесь). После включения automaticSilentRenew
и короткий AccessTokenLifetime
Я вижу, как тихие запросы запускаются в консоли браузера, как я и ожидал.
Я вижу два последующих вызова конечной точки UserInfo IS4 (см. Скриншот ниже). Первый - предпечатная проверка CORS OPTIONS
запрос. В точке останова в моей пользовательской реализации IProfileService.IsActiveAsync()
Я вижу, что этот запрос успешно проходит проверку подлинности (путем проверки httpContext
).
public class ProfileService : IProfileService
{
private readonly HttpContext _httpContext;
public ProfileService(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
...
public async Task IsActiveAsync(IsActiveContext context)
{
var temp = _httpContext.User; // breakpoint here
// call external API with _httpContext.User info to get isActive
}
}
Впрочем, второй запрос (GET
) к конечной точке UserInfo не проходит аутентификацию. Моя точка останова в IProfileService.IsActiveAsync()
не показывает аутентификацию пользователя, поэтому моя процедура проверки, активен ли пользователь (вызов другого API), возвращает false, что переводится в 401. Я вижу этот заголовок при сбое GET
запрос WWW-Authenticate: error="invalid_token"
,
Я попытался указать IdentityTokenLifetime
это меньше чем AccessTokenLifetime
по этому безуспешно.
Вот журналы двух запросов:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 OPTIONS http://localhost:5000/connect/userinfo
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 8.5635ms 204
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:5000/connect/userinfo
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
IdentityServer4.Hosting.IdentityServerMiddleware:Information: Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo
IdentityServer4.Validation.TokenValidator:Error: User marked as not active: f84db3aa-57b8-48e4-9b59-6deee3d288ad
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 94.7189ms 401
Вопрос:
Как я могу получить GET
запрос к конечной точке UserInfo во время тихого обновления для аутентификации в HttpContext
?
Обновить:
Добавление скриншотов всех заголовков двух запросов и получаемых файлов cookie браузера для расследования ответа @Anders.
2 ответа
Хорошо, так что я думаю, что у меня есть представление о том, что происходит. Вы делаете запрос на автоматическое обновление токена (что успешно видно из перенаправления на oidc-silent-refresh.html), и вызывается первый IsActiveAsync в ProfileService (поскольку он должен пройти через службу профиля, чтобы сгенерировать токены для тихое обновление). Вы смогли увидеть "HttpContext.User", потому что был открыт iframe, который позволял отправлять все необходимые файлы cookie.
Запрос предварительной проверки информации о пользователе даже не поступил в ProfileService, как вы можете видеть из журналов, имеющих только один раз "Вызывающую конечную точку IdentityServer: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo". Кроме того, UserInfoEndpoint предотвращает прохождение любого запроса опций (который вы можете проверить, чтобы быть истинным в начале метода ProcessAsync здесь).
Теперь, когда вы делаете фактический запрос информации о пользователе, вы пытаетесь получить информацию из HttpContext (который обычно заполняется с использованием информации из файла cookie), но он недоступен, поскольку в запросе не отправляются файлы cookie. Запрос делается здесь в методе 'getJson' (из библиотеки oidc-client-js), и вы можете видеть, что в запросе не отправляются файлы cookie (свойство 'withCredentials' будет установлено в true в запросе, если они мы).
Так как же получить необходимую информацию о пользователе? Чтобы получить эту информацию, мы можем обратиться к " контексту", переданному в "IsActiveAsync" ProfileService, который содержит принципала, заполненного утверждениями токена доступа (этот процесс можно увидеть в методе "ValidateAccessTokenAsync" здесь).
Запрос на /connect/userinfo
аутентифицируется с помощью куки-файла аутентификации сеанса в домене / пути IdentityServer.
Я предполагаю, что файл cookie правильно включен в запрос OPTIONS, но не включен в последующий запрос GET. Это то, что вы можете проверить в инструментах разработчика браузера, посмотрев на запрос.
Если мое предположение верно, причина, вероятно, в том же атрибуте сайта. Все файлы cookie авторизации в ASP.NET Core (которые использует IdentityServer4) по умолчанию имеют атрибут одинакового сайта, чтобы предотвратить атаки подделки межсайтовых запросов.
В соответствии с информацией, которую я могу найти, cookie с samesite=lax не разрешены в запросе AJAX Get. Однако я не могу найти ничего, если это разрешено в запросе OPTIONS. Вы можете проверить (с помощью инструментов разработчика browswer), есть ли в заголовке cookie параметр одинакового сайта в ответе на первый запрос /connect/authorize
,
Сам параметр находится в Cookie
раздел параметров cookie в вызове AddCookie()
, MS документы говорит, что по умолчанию lax
,
С другой стороны, в репозитории Idsrv4 есть поток GitHub, в котором говорится, что они изменили значение по умолчанию "none" для файла cookie сеанса Idsrv4.
Я, очевидно, немного догадываюсь здесь, но это должно быть довольно просто, чтобы проверить мои предположения, как изложено выше.