[Из песочницы] Поддержка анонимных jwt токенов в IdentityServer4 при помощи AnonymousIdentity

vdvvq_xuyawe8apt2pdn4flentk.png
Недавно мне потребовалось реализовать поддержку анонимной аутентификации пользователей на основе 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": ,
  "access_token": ,
  "token_type": "Bearer",
  "expires_in": "2592000",
  "scope": "openid email api1",
  "state": "abc",
  "session_state": 
}


Authorization Code Flow

При таком подходе, точка авторизации отвечает в виде Json, включая код авторизации.

{
  "code": ,
  "scope": "openid email api1",
  "state": "abc",
  "session_state": 
}

Затем, необходимо обменять код на токен стандартным методом.

Формируем запрос к конечной точке токена.

POST /connect/token
    client_id=client2&
    client_secret=secret&
    grant_type=authorization_code&
    code=`&
    redirect_uri=https://myapp/callback

В результате, конечная точка токена отвечает в виде Json, включая токен доступа.

{
  "id_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.

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

© Habrahabr.ru