Недавно мне потребовалось реализовать поддержку анонимной аутентификации пользователей на основе OpenId Connect и OAuth 2.0 на платформе ASP.NET Core. Здесь не будет объясняться спецификация данных протоколов, для этого есть полно статей на хабре. Перейдем к сути.
Зачем нужен анонимный токен? Для авторизации анонимного пользователя на API ресурсе, особенно в архитектуре микросервисов, к тому же, он может изменить состояние нашего приложения, например, Васе понравилась футболка с котиками, он добавляет ее в корзину интернет-магазина и, возможно, оформляет заказ в качестве гостя. Чтобы понять, что это был именно Василий, анонимный токен содержит идентификатор анонимного пользователя и идентификатор сессии. Когда Василий войдет в систему, эти параметры будут включены в аутентифицированный токен.
К тому же, анонимный токен доступа может содержать любые дополнительные утверждения(или claims).
Инструменты
- IdentityServer4 для поддержки OpenId Connect и OAuth 2.0
- AnonymousIdentity для поддержки анонимных токенов в IdentityServer4
Имплементация
IdentityServer4 имеет множество примеров на GitHub.
Добавить поддержку анонимных токенов достаточно просто:
- Установить AnonymousIdentity в проект с IdneitityServer4
Install-Package AnonymousIdentity
- Зарегистрировать AnonymousIdentity в Startup.cs
services.AddIdentityServer() // остальные регистрации .AddAnonymousAuthentication();
Сценарий взаимодействия
Рассмотрим получение анонимного токена для Implicit Flow и Authorization Code Flow.
Запрос к точке авторизации
Чтобы следовать спецификации, запрос к точке авторизации для Implicit Flow выглядит следующим образом.
GET /connect/authorize?
client_id=client1&
scope=openid email api1&
response_type=id_token token&
redirect_uri=https://myapp/callback&
state=abc&
nonce=xyz&
acr_values=0&
response_mode=json
Для Authorization Code Flow аналогичный запрос с response_type = code (PKCE опционально).
Различия между обычным и анонимным запросом в двух параметрах:
- Параметр acr_values = 0 сигнализирует об анонимном входе в систему. Если интересно, можно почитать спецификацию OpenId Connect.
- Параметр response_mode = json служит для ответа в виде Json без лишних перенаправлений.
Получение токена
В зависимости от состояния аутентификации, возвращается либо анонимный, либо аутентифицированный токен.
Implicit Flow
В данном случае, точка авторизации отвечает в виде Json, включая токен доступа.
{
"id_token": <id_token>,
"access_token": <access_token>,
"token_type": "Bearer",
"expires_in": "2592000",
"scope": "openid email api1",
"state": "abc",
"session_state": <optional>
}
Authorization Code Flow
При таком подходе, точка авторизации отвечает в виде Json, включая код авторизации.
{
"code": <authorization_code>,
"scope": "openid email api1",
"state": "abc",
"session_state": <optional>
}
Затем, необходимо обменять код на токен стандартным методом.
Формируем запрос к конечной точке токена.
POST /connect/token
client_id=client2&
client_secret=secret&
grant_type=authorization_code&
code=`&
redirect_uri=https://myapp/callback
В результате, конечная точка токена отвечает в виде Json, включая токен доступа.
{
"id_token": <id_token>,
"access_token": <access_token>,
"token_type": "Bearer",
"expires_in": "2592000",
"scope": "openid email api1"
}
Сравнение анонимного и аутентифицированного токена
Если сервер авторизации не содержит аутентификационных данных, мы получаем анонимный токен.
{
"nbf": 1566849147,
"exp": 1569441147,
"iss": "https://server",
"aud": [
"https://server/resources",
"api"
],
"client_id": "client1",
"sub": "abda9006-5991-4c90-a88c-c96764027347",
"auth_time": 1566849147,
"idp": "local",
"ssid": "9e6453dbaf5ffdb03f08812f759d3cdf",
"scope": [
"openid",
"email",
"api1"
],
"amr": [
"anon"
]
}
Определить, что пользователь является анонимным можно по методу аутентификации(amr).
Идентификатор "общей" сессии(ssid) и идентификатор субъекта(sub) будут включены в аутентифицированный токен, при последующем входе пользователя в систему.
В случае, если пользователь выполнил вход в систему, мы получаем аутентифицированный токен.
{
"nbf": 1566850295,
"exp": 1566853895,
"iss": "https://server",
"aud": [
"https://server/resources",
"api"
],
"client_id": "client1",
"sub": "bob",
"auth_time": 1566850295,
"idp": "local",
"aid": "abda9006-5991-4c90-a88c-c96764027347",
"ssid": "9e6453dbaf5ffdb03f08812f759d3cdf",
"scope": [
"openid",
"email",
"api1"
],
"amr": [
"pwd"
]
}
Как вы можете заметить, идентификатор анонимного пользователя(aid) совпадает с sub анонимного токена, так же как и ssid. Если клиент не инициировал анонимный вход, то аутентифицированный токен будет содержать только ssid.
Таким образом, мы можем авторизовать анонимного пользователя и идентифицировать его действия после входа в систему.
Заключение
В данной статье мы рассмотрели сценарий получения анонимного/аутентифицированного токена с помощью IdentityServer4 c расширением AnonymousIdentity.
Если есть вопросы, буду рад ответить на них в комментариях.