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


Если есть вопросы, буду рад ответить на них в комментариях.

Комментарии (0)