
Недавно мне потребовалось реализовать поддержку анонимной аутентификации пользователей на основе 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.
Если есть вопросы, буду рад ответить на них в комментариях.
