Добавление нового провайдера OAuth-авторизации

Регистрация приложения на сервере авторизации

xref:api:rest/v1/model/oauth/index.adoc

Рекомендация. Прежде чем подключать нового специфического провайдера OAuth-авторизации, полезно попрактиковаться и освоить процесс, подключив Яндекс.ID

Приложение (коммуникационную систему) необходимо зарегистрировать у провайдера авторизации и получить идентификаторы, которые будут использоваться в дальнейшем при взаимодействии между приложением и сервером авторизации. Обычно в рамках заявки среди прочих данных надо передать 'redirect_uri' и 'scope'.

redirect_uri

'redirect_uri' - URL или список из нескольких URL, на которые разрешены перенаправления после успешно проведенной авторизации. Следует указать "https://<URL_DOMAIN>/oauth/receiver", либо просто "https://<URL_DOMAIN>". Обратите внимание, что не все провайдеры разрешают перенаправления на нешифрованный http, но некоторые позволяют. В качестве URL_DOMAIN необходимо указать то DNS имя или адрес, по которому в систему попадают все без исключения пользователи. Это может быть как публичное имя, так и имя, известное внутри-сетевому DNS. При этом в сущность провайдера авторизации URL надо указать "https://<URL_DOMAIN>/oauth/receiver". Обязательно, чтобы хотя бы один из переданных на регистрацию URL совпадал или был префиксом от этого.

{
    ...
    "redirect_uri": "https://pbx.era-platform.ru/oauth/receiver",
    ...
}

scope

'scope' - требуемый объем информации о пользователе. Необходимо выяснить предоставляемый сервером авторизации состав параметров, и выбрать тот перечень, что соответствуют самой минимальной авторизации. Каждый провайдер авторизации имеет уникальный состав опций. Выбрав необходимые значения, следует задать их также в сущности провайдера авторизации. Например для Яндекс.ID это:

{
    ...
    "scope": [
      "login:info",
      "login:email"
    ],
    "optional_scope": [],
    ...
}

Главное при выборе скоупа, чтобы сервисом авторизации предоставлялся уникальный логин. По логину коммуникационная система связывает авторизацию с собственной учетной записью пользователя или вовсе может создать этого пользователя. Если явного поля логина нет, значит его предстоит состряпать уникальным образом из того, что предоставляется. Для этого необходимо будет создать и использовать служебный сценарий авторизации по токену, установленный в параметрах мастер-домена (iam_token_svcscript_code), а в нем из предоставленной сервером авторизации информации выбрать значения и сформировать уникальный логин, присвоить его переменной сценария "login".

Другие опции

Если доменов много и сервер авторизации различает доменную ориентацию в рамках коммуникационной платформы, то необходимо чтобы возвращались либо имя домена, либо какой-то иной параметр, по которому можно однозначно определить домен коммуникационной платформы, к которому этот пользователь привязан. Если нет, то в сущности провайдера авторизации надо указать свойство 'default_domain'. Неявное определение домена также предстоит производить в служебном сценарии авторизации по токену (iam_token_svcscript_code), присвоить его переменной сценария "domain".

{
    ...
    "default_domain": "users.pbx.era-platform.ru",
    ...
}

Из нестандартных опций запроса может понадобиться указать, чтобы access_token передавался в формате JWT. Некоторые провайдеры делают это по умолчанию, а некоторые только по опции.

Результат регистрации

После регистрации приложения на сервере авторизации им предоставляются значения client_id и client_secret. Их следует внести в сущность провайдера авторизации:

{
    ...
    "client_id": "1ecf9d9aaf3248...",
    "client_secret": "c46b732a8...",
    ...
}

Заполнение сущности провайдера

По результатам регистрации создается сущность провайдера авторизации (/rest/v1/model/oauth/Providers). В сущности провайдера авторизации на этом этапе заполняются свойства 'client_id', 'client_secret', 'default_domain', 'scope', 'optional_scope'.

Параметры сопоставления с учетной записью пользователя платформы

Далее в сущности провайдера авторизации следует задать, какие поля в ответе рест-сервера системы авторизации соответствуют во-первых логину пользователя, во-вторых имени пользователя, в-третьих домену, и в четвертых прочим полям (емайлу, и т.д.). Некоторые другие значения также могут примениться и попасть в сущность пользователя (при создании или апдейте его учетной записи). Например для Яндекс.ID в сущности провайдера авторизации заданы:

{
    ...
    "query_login": [
      "login"
    ],
    "query_name": [
      "name",
      "real_name"
    ],
    "query_email": [
      "email",
      "emails/0"
    ],
    "query_domain": null,
    "query_id": null,
    "query_info": null,
    ...
}

Получив ответ рест-сервера в соответствии с указанными правилами (поля 'query_*') будет парситься и сопоставляться соответствующее значение. Результаты попадают в сущность запроса на авторизацию, а оттуда либо напрямую в механизм сопоставления, либо в служебный сценарий авторизации по токену, где могут быть еще дополнительно преобразованы.

Базовые свойства провайдера

Также в сущности провайдера следует указать базовые параметры (их описание доступно в справке https://vendor.era-platform.ru/docs/era/latest/api/rest/v1/model/endpoints/oauth/providers.html). Например, для Яндекс.ID это:

{
    ...
    "id": "0c04b29b-0184-31f2-2e5e-3cecef28bebf",
    "key": "yandex",
    "label": "Вход c Яндекс ID",
    "enabled": true,
    "order": null,
    "icon_uri": "/.well-known/oauth/icons/ya.png",
    "dialect": "oauth",
    ...
}

Иконку ya.png следует вручную разместить в каталоге ':SYNC/common/www/.well-known/oauth/icons' и указать путь к ней в свойстве 'icon_uri'.

Прочие свойства сущности рассматриваются дальше в контексте многошагового процесса авторизации.

Процесс авторизации

1. Перенаправление на сервер авторизации

При наличии включенных провайдеров авторизации в форме логина появляются соответствующие кнопки. Пользователь может выбрать стандартную авторизацию или пройти через внешнего провайдера авторизации. Если необходимо всех пользователей направить на внешнего провайдера авторизации, то следует заблокировать возможность авторизации напраямую через логин-пароль. Это можно сделать, например, активировав сценарий внешней авторизации в мастер-домене (iam_general_svcscript_code), в котором присвоить в переменную "result" значение 0 (авторизация запрещена).

Сначала согласно процедуре при клике на кнопке внешней авторизации производится переадресация на сервер авторизации, заданный в 'uri_authorize'. Для Яндекс.ID это:

{
    ...
    "uri_authorize": "https://oauth.yandex.ru/authorize",
    ...
}

При переадресации на сервер авторизации в URL подставляются и передаются следующие стандартные OAuth параметры:

    response_type = code
    client_id = <Account's client_id>
    scope = <Account's scope>
    optional_scope = <Account's optional_scope>
    redirect_uri = <Account's redirect_uri>
    state = <Unique request identifier>

В параметры подставляется response_type = code - константа, определяющая стандартный путь: сервер авторизации сгенерирует код и передаст его дополнительным параметром во время дальнейшей переадресации, а по коду уже будет получен токен.

Параметр 'state' из URL переадресации генерируется платформой под конкретный запрос внешней авторизации и служит для сопоставления разных частей процесса при переадресациях. При этом в зависимости от параметра 'state_mode' из сущности, параметр 'state' из URL может подставиться либо просто как еще один параметр, либо быть включен как параметр именно в redirect_uri. Это зависит от того, производит ли сервер авторизации автоматический проброс параметра 'state' при перенаправлении или нет, а также позволяет ли он указывать 'redirect_uri' с расширением относительно заданного при регистрации, либо требует строгого их соответствия. Например, для Яндекс.ID:

{
    ...
    "state_mode": "param"
    ...
}

При необходимости следует задать дополнительные параметры для кастомизации конкретного провайдера авторизации. Они подставятся в URL перенаправления без изменений. Например, для Яндекс.ID:

{
    ...
    "params_authorize": {
      "display": "popup",
      "force_confirm": "yes"
    },
    ...
}

На этом шаге управление процессом авторизации передается серверу авторизации, а в коллекции /rest/v1/model/oauth/Requests добавляется сущность запроса в состоянии 'initial'.

2. Авторизация на внешнем сервере OAuth

Веб-страница пользователя отображает окно авторизации подключенного сервера OAuth, где пользователю предлагается ввести логин и пароль и, возможно, согласиться с предоставлением приложению определенных сведений о пользователе (тот самый scope). В случае успеха сервер авторизации производит обратное перенаправление на страницу приложения, указанную в 'redirect_uri'.

Если пользователь ранее уже авторизовывался и у него еще активна сессия на сервере авторизации, то этот шаг протекает незаметно, и сервер авторизации сразу производит обратное перенаправление.

3. Обратное перенаправление после авторизации, получение токена доступа

Управление передается обратно на страницу по указанному выше 'redirect_uri'.

Соответственно, веб-страница пользователя снова обращается к веб-серверу коммуникационной платформы (это /oauth/receiver). Веб-сервер, обрабатывая полученное обращение с переданным параметром 'state' (идентификатор конкретного запроса авторизации в коммуникационной платформе) и 'code' (идентификатор сессии авторизации на сервере авторизации), обращается на сервер авторизации за токеном доступа и передает среди параметров POST-запроса значение 'client_secret'.

Поскольку это server-side request, протоколом OAuth исключается возможность перехвата client_secret снаружи конкретным пользователем. (Кстати, в этом месте в ЕСИА добавлено дополнительное усложнение, используются ГОСТ-сертификаты, специальным образом формируются параметры)

Обращение к серверу авторизации за токеном доступа производится на адрес, указанный в свойстве 'uri_token'. например, для Яндекс.ID это:

{
    ...
    "uri_token": "https://oauth.yandex.ru/token",
    ...
}

POST-запрос осуществляется с передачей следующих параметров

   grant_type = authorization_code
   code = <Authorization code from redirected URL>
   client_id = <Account's client_id>
   client_secret = <Account's client_secret>
   redirect_uri = <Account's redirect_uri

В параметры подставляется grant_type = authorization_code - константа, это стандартный механизм (и платформа в текущей версии поддерживает только его).

Сервер авторизации в случае успеха возвращает 200 и в теле ответа JSON с токеном доступа.

4. Получение информации о пользователе

Веб-сервер коммуникационной платформы совершает GET-запрос к REST-серверу системы авторизации, подставляет туда заголовок

Authorization: OAuth <Access Token>.

Запрос производится по адресу, указанному в свойстве 'uri_info':

{
    ...
    "uri_info": "https://login.yandex.ru/info?format=json",
    ...
}

Ответ приходит в формате JWT. Если веб-сервер коммуникационной платформы фиксирует сбой проверки хеша (в лог-журнале: "Invalid JWT signature"), то в сущность провайдера можно задать свойство 'verify_hash' и отключить верификацию JWT-хеша:

{
    ...
    "verify_hash": false,
    ...
}

Веб-сервер коммуникационной платформы распаковывает JWT, в соответствии с правилами 'query_*' производит разбор целевых значений. В случае успеха изменяет состояние ('authorized') и состав запроса авторизации, наполняя его разобранным содержимым полученного ответа.

На этом сеанс взаимодействия с сервером авторизации завершается. Далее происходит уже привязка к учетной записи пользователя и создание сессии в коммуникационной платформе.

5. Перенаправление на страницу связывания

Веб-страница пользователя перенаправляется на /oauth/enter/<RequestId>.

Свойства 'login_mode', 'register_user_enabled', 'update_user_enabled' определяют способ связывания ответа сервера авторизации с учетной записью пользователя системы.

Значение login_mode=auto использует указанный 'default_domain' (или явно выданный сервером авторизации домен и разобранный с помощью 'query_domain'), ищет по соответствию выданного сервером авторизации значения 'login' в этом домене (разобраного с помощью 'query_login'), и создает сессию веб-сервера.

  • Если пользователь с этим логином не найден в этом домене, то при установленном разрешении одновременно в свойстве сущности провайдера 'register_user_enabled' и в параметре домена 'self_register_allowed' учетная запись может быть там создан. При создании используется шаблон учетной записи пользователя, заданный в параметре домена 'self_register_template'.

  • Если учетная запись пользователя обнаружена, то при установленном разрешении в свойстве сущности провайдера 'update_user_enabled' она обновляется на основании актуальных предоставленных сервером авторизации сведений о пользователе и разобранных правилами 'query_*'.

Значение login_mode=script использует сценарий авторизации по токену (берется из свойства iam_svcscript_code или, если там пусто из параметра мастер-домена 'iam_token_svcscript_code'). В качестве первого параметра передается идентификатор запроса авторизации, вторым параметром JSON-представление сущности запроса авторизации, третьим параметром JSON-представление сущности провайдера авторизации, четвертым параметром IP-адрес и порт клиента. Для успешного связывания и авторизации сценарий присваивает значение 1 переменной "result", а переменным "login" и "domain" присваивает соответствующие значения, по которым далее система может найти учетную запись пользователя. При необходимости такая учетная запись должна быть создана в ходе выполнения сценария. Логин и домен могут также формироваться произвольным образом на основании сведений о пользователе, предоставленных сервером авторизации и сохраненных в запросе на авторизацию (второй параметр сценария).

6. Создание сессии

В результате на переадресованный запрос отправляется ответ - главная страница с приложениями, в который устанавливается сессионная cookie. Все дальнейшие запросы осуществляются веб-страницей с передачей сессионной cookie.

Некоторые дополнения

В случае, если процесс где-то неожиданно прерывается, запрос авторизации автоматически удаляется из коллекции /rest/v1/model/oauth/Requests спустя несколько секунд.

В описанном выше сценарии отсутствует базовая проверка, не авторизован ли пользователь уже, может быть cookie сервера авторизации уже присутствует в запросе, и нужно провести только считывание сведений и связывание, обойтись без избыточных переадресаций. Как правило в этом случае переадресации проходят насквозь без отображения окна ввода логина и пароля, и это остается незаметным для пользователя. Однако этот момент может быть доработан в коммуникационной платформе.