Перейти к содержанию

Интеграция с сервисом Multiapp платформы "Ключ"

Оглавление

Обзор

Данный документ описывает процесс интеграции партнёрских приложений с платформой Ключ с использованием сервиса Multiapp. Цель интеграции — реализация сквозной авторизации (Single Sign-On, SSO), позволяющей авторизованным пользователям платформы Ключ бесшовно переходить в приложение партнёра и быть там автоматически авторизованными.

Предлагаемый нами подход к авторизации, основанный на обмене кодом (code) на зашифрованный идентификационный токен пользователя (id_token), основан на практиках стандарта OpenID Connect (OIDC). Сейчас наша реализация имеет свои особенности, отличающие её от стандартных процессов OIDC (например, Authorization Code Flow), мы стремимся к её дальнейшему развитию и приближению к стандартным сценариям в будущем.

Ключевые отличия текущей реализации от стандартного OIDC Authorization Code Flow
  • Инициация: Процесс запускается платформой "Ключ" (IdP-initiated), а не инициируется партнёром (RP-initiated) через перенаправление на эндпоинт авторизации.
  • Специфичные эндпоинты: В связи с использованием gRPC Gateway на платформе Ключ, API-эндпоинты не в полной мере соотвествуют стандарту (например: /multiapp/api/v1/partner/token вместо стандартного /token).
  • Обязательное шифрование id_token: Идентификационный токен всегда передается в зашифрованном виде с использованием JWE (Nested JWT), тогда как в стандартном сценарии OIDC он чаще только подписан (JWT).
  • Стандартные параметры в разработке: Стандартные параметры OIDC, такие как scope, state, client_id/client_secret для аутентификации клиента при запросе токена, и response_type в его классическом понимании для эндпоинта авторизации возможно будут реализованы позднее.

Основным механизмом на данный момент является получение и валидация id_token партнёром, который передаётся в формате Nested JWT (JWE, шифрующий подписанный JWT).

Документация предназначена для разработчиков на стороне партнёра и описывает текущий процесс взаимодействия.

Внешний вид раздела "Сервисы

Глоссарий и Акторы

Акторы

  • Пользователь — Конечный пользователь платформы "Ключ", который инициирует переход в приложение партнёра.
  • Партнёр — Сторонняя компания или сервис, интегрирующий своё приложение с платформой "Ключ".
  • Фронтенд партнёра — Веб- или мобильное приложение партнёра, с которым взаимодействует пользователь.
  • Бэкенд партнёра — Серверная часть приложения партнёра, отвечающая за бизнес-логику, взаимодействие с API Ключа и управление пользовательскими данными.
  • Платформа "Ключ" — Экосистема сервисов Ростелекома "Ключ".
  • Фронтенд Ключа — Веб-интерфейс или мобильное приложение "Ключ", откуда пользователь переходит к партнёру.
  • Бэкенд Ключа — Серверная часть платформы Ключ, представленная различными сервисами, включая:
    • Сервис Multiapp — Сервис, отвечающий за генерацию кодов авторизации и выдачу токенов партнёрам.
    • Сервис JWKS — Сервис, предоставляющий публичные ключи для валидации подписи токенов.
  • Менеджер — Представитель платформы "Ключ", ответственный за регистрацию партнёра и выдачу первичных учётных данных.

Термины

Термин Описание
API Ключ (Партнёра) Секретный ключ, выдаваемый Менеджером партнёру для первоначальной аутентификации в key-cli.
key-cli Утилита командной строки (репозиторий), предоставляемая платформой "Ключ" для генерации партнёром собственной пары асимметричных ключей и регистрации публичного ключа на платформе.
Пара ключей шифрования Асимметричная криптографическая пара ключей (публичный и приватный), сгенерированная Партнёром с помощью key-cli. Используется для шифрования/дешифрации JWE.
Приватный ключ Секретный ключ, хранится у партнёра на бэкенде, используется для расшифровки JWE.
Публичный ключ Открытый ключ, передаётся партнёром на платформу "Ключ" через key-cli. Платформа использует его для шифрования JWE специально для этого партнёра.
Код авторизации (code) Одноразовый короткоживущий код, генерируемый Ключом. Передаётся партнёру через редирект пользователя. Используется для обмена на id_token.
Идентификационный токен (id_token) Токен в формате Nested JWT (JWT внутри JWE). Содержит зашифрованную информацию о пользователе. Предназначен для Бэкенда партнёра.
JWE (JSON Web Encryption) Стандарт шифрования данных в формате JSON (RFC 7516). Используется как "внешняя оболочка" id_token, зашифрованная публичным ключом партнёра.
JWT (JSON Web Token) Стандарт для создания токенов, содержащих утверждения (claims) в формате JSON (RFC 7519). Используется как "внутреннее содержимое" id_token, содержит данные пользователя. Подписан приватным ключом Ключа.
JWK (JSON Web Key) Стандарт представления криптографических ключей в формате JSON (RFC 7517).
JWKS (JSON Web Key Set) Набор ключей JWK (RFC 7517 Section 5). Платформа "Ключ" публикует свои публичные ключи подписи (JWKS) для валидации подписи JWT партнёрами.
Nested JWT Конструкция, где JWT является полезной нагрузкой (payload) для JWE. Внешняя оболочка (JWE) шифруется публичным ключом партнёра, внутренняя (JWT) подписывается отправителем (Ключом).

Пререквизиты для обеспечения взаимодействия

Для начала интеграции Партнёру необходимо выполнить следующие шаги:

  1. Передать Менеджеру ссылки:
    • Для веб-приложения: URL страницы, куда будет перенаправлен пользователь из Ключа (редирект URL).
    • Для мобильного приложения: диплинк на экран, куда будет перенаправлен пользователь.
  2. Менеджер производит регистрацию партнёра, его приложений и создаёт для партнёра шаблоны уведомлений на платформе Ключ.
  3. Менеджер передаёт Партнёру:
    • API ключ. Этот ключ нужен для первоначальной аутентификации в утилите key-cli и для отправки push-уведомлений. Храните его безопасно.
    • Идентификаторы приложений партнёра и идентификаторы шаблонов эти идентификаторы понадобятся для отправки push-уведомлений.
  4. Используя полученный на шаге 3 API ключ, партнёр должен запустить утилиту key-cli через Docker с командой partners register для генерации пары ключей шифрования и автоматической регистрации публичного ключа на платформе Ключ.

    Пример команды key-cli для генерации и регистрации ключей:

    # Замените <X-API-KEY> на ваш реальный API ключ, полученный от Менеджера
    # Папка ./files будет создана в текущем каталоге и будет содержать приватный ключ
    docker run -it -v ${PWD}/files:/app/files \
      registry.infra.rtkit.dev/openapi/key-cli/main:latest \
      partners register --key <X-API-KEY>
    

    Для работы с тестовым окружением Ключа добавьте флаг -e DOTNET_ENVIRONMENT=Staging:

    docker run -it -v ${PWD}/files:/app/files \
      -e DOTNET_ENVIRONMENT=Staging \
      registry.infra.rtkit.dev/openapi/key-cli/main:latest \
      partners register --key <X-API-KEY>
    
    • Утилита сохранит приватный ключ в файле внутри папки files в вашем текущем каталоге.
    • Приватный ключ потребуется на Шаге 4 для расшифровки id_token.

Примечание

Приведенные здесь команды key-cli описывают основной сценарий первоначальной регистрации. Полный список команд, опций и актуальная информация по работе с утилитой key-cli представлена в её репозитории: https://gitlab.infra.rtkit.dev/openapi/key-cli (или в предоставленной вам документации к key-cli).

Сценарий получения идентификационного токена партнёром

Диаграмма последовательности взаимодействия с платформой Ключ
sequenceDiagram
    autonumber
    participant User as "Пользователь Ключа"
    box "Платформа Ключ"
        participant KeyFE as "Фронтенд Ключа"
        participant MultiApp as "Бэкенд Ключа"
    end
    box "Сторона партнёра"
        participant PartnerFE as "Фронтенд партнёра"
        participant PartnerBE as "Бэкенд партнёра"
    end

    User ->> KeyFE: Выбирает приложение партнёра
    KeyFE ->> MultiApp: GET /multiapp/api/v1/app/{app_id}/code
    MultiApp -->> KeyFE: HTTP 200 <GetAppCodeResponse>
    Note right of KeyFE: Шаг 1. Получение кода авторизации

    KeyFE -->> User: Редирект на приложение партнёра
    alt Если тёмная тема включена
        User ->> PartnerFE: GET /partner-app?code={GetAppCodeResponse.data}&mode=dark
    else
        User ->> PartnerFE: GET /partner-app?code={GetAppCodeResponse.data}
    end
    PartnerFE ->> PartnerFE: Шаг 2. Проверка авторизации (cookie/local storage)

    alt Пользователь не авторизован
        PartnerFE ->> PartnerBE: передача кода из query параметра на бэкенд партнёра
        PartnerBE ->> MultiApp: GET /multiapp/api/v1/partner/token?code={GetAppCodeResponse.data}&response_type=id_token&nonce={random_guid}
        MultiApp -->> PartnerBE: HTTP 200 <GetPartnerTokenResponse>
        Note over PartnerBE, MultiApp: Шаг 3. Запрос идентификационного токена
        PartnerBE ->> PartnerBE: Шаг 4. Расшифровка id_token
        PartnerBE ->> MultiApp: GET /jwks/api/v1/key/.well-known/jwks.json
        MultiApp -->> PartnerBE: HTTP 200 <GetKeyWellKnownJwksJsonResponse>
        Note over PartnerBE, MultiApp: См. раздел "Кэширование публичных JWK-ключей"
        PartnerBE ->> PartnerBE: Шаг 5. Валидация токена
        PartnerBE ->> PartnerBE: Поиск пользователя по sub, orpon+room, phone_number, email
        Note over PartnerBE: Шаг 6. Обработка и синхронизация данных пользователя
        alt Пользователь не существует
            PartnerBE ->> PartnerBE: Создание новой учётной записи (на основе данных из id_token)
            PartnerBE ->> PartnerBE: Авторизация пользователя (генерация токена доступа)
            PartnerBE -->> PartnerFE: Пользователь авторизован
            PartnerFE ->> User: Перенаправление к функционалу
        else Пользователь существует
            PartnerBE ->> PartnerBE: Обновление данных пользователя (если требуется)
            Note over PartnerBE: Шаг 6. Обработка и синхронизация данных пользователя
            PartnerBE ->> PartnerBE: Авторизация пользователя (генерация токена доступа)
            PartnerBE -->> PartnerFE: Пользователь авторизован
            PartnerFE ->> User: Перенаправление к функционалу
        end
    else Пользователь уже авторизован
        alt Первый вход через Ключ
            PartnerFE ->> PartnerBE: передача кода из query параметра на бэкенд партнёра
            PartnerBE ->> MultiApp: GET /multiapp/api/v1/partner/token?code={GetAppCodeResponse.data}&response_type=id_token&nonce={random_guid}
            MultiApp -->> PartnerBE: HTTP 200 <GetPartnerTokenResponse>
            Note over PartnerBE, MultiApp: Шаг 3. Запрос идентификационного токена
            PartnerBE ->> PartnerBE: Шаг 4. Расшифровка id_token
            PartnerBE ->> MultiApp: GET /jwks/api/v1/key/.well-known/jwks.json
            MultiApp -->> PartnerBE: HTTP 200 <GetKeyWellKnownJwksJsonResponse>
            Note over PartnerBE, MultiApp: См. раздел "Кэширование публичных JWK-ключей"
            PartnerBE ->> PartnerBE: Шаг 5. Валидация токена
            PartnerBE ->> PartnerBE: Сохранение данных пользователя
            Note over PartnerBE: Шаг 6. Обработка и синхронизация данных пользователя
            PartnerBE -->> PartnerFE: Данные обновлены
            PartnerFE ->> User: Перенаправление к функционалу
        else Ранее входил через Ключ
            PartnerFE ->> User: Перенаправление к функционалу
        end
    end

Шаг 1. Получение кода авторизации (выполняется на стороне фронтенда "Ключ")

Когда Пользователь в интерфейсе Ключа выбирает переход в приложение Партнёра, Фронтенд Ключа запрашивает у Бэкенда Ключа одноразовый код авторизации (code).

Фронтенд Ключа -> Сервис Multiapp:GET /multiapp/api/v1/app/{app_id}/code (1)

  1. 📘 Подробнее см.:
    Swagger UI
    OpenAPI документация
Пример запроса
1
2
3
4
5
# Этот запрос выполняется системой Ключ, пример для иллюстрации
curl --location 'https://keyapis.key.rt.ru/multiapp/api/v1/app/8940e063-7284-4d37-bef1-8762c9be64f3/code' \
--header 'X-Request-Id: 01a4d9c5-05a6-43fe-a25e-c96015beee97' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpiZDUyMGZlZS1hZDYxLTQzYTUtYjM1Mi04MTAwOGUyMTU4OTAiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOltdLCJjbGllbnRfaWQiOiJiV0Z6ZEdWeU9qSXlORGszTXpvNU56YzFORG95TVRZNk9ESXdNam82T25nd00yNDJia3RNT0cxS2JGTk1kbkEwUlhRemFVUnFZMmhOTjJGaWRFVkxNVVpaZGxreFluSllVSGM5IiwiZXhwIjoxNzQ0NzI3MjE5LCJleHQiOnt9LCJpYXQiOjE3MTMxOTEyMTksImlzcyI6Imh0dHBzOi8vb2F1dGgyLXN0YWdpbmcuazhzLmtleS5ydC5ydS8iLCJqdGkiOiI4NDJkMDM5My02ZjYzLTRlMzItYWI3ZC1kYTRkMDM2NDdhZTAiLCJuYmYiOjE3MTMxOTEyMTksInNjcCI6W10sInN1YiI6ImJXRnpkR1Z5T2pJeU5EazNNem81TnpjMU5Eb3lNVFk2T0RJd01qbzZPbmd3TTI0MmJrdE1PRzFLYkZOTWRuQTBSWFF6YVVScVkyaE5OMkZpZEVWTE1VWlpkbGt4WW5KWVVIYzkifQ.F94-Xv4ai5ZPUbxhlHXYwDLlctREd0Bh-HtIPx_KFHFAOiepYGwMR_Q0N8QAz1GuMGx0WN-fl6BQ-VXgHvbpAW_OE2CCGJMjosmImRFWkuObVq-ZHRuaKK5KV-YCOVRXyPFY3vQVpTB3bKCCIe6sAOM6ODMk8_Uxt7TjExn9mRhz2mqHbqhqQHkHuNLQtbPL2xRD9Th-eHEA-lg_0Z76hGgILsfvG3-C0ivivJebcA8uuCNtChZmxi8K19qG4bTDJZI_KdhP7ODFfYUjDksplZPBM5dPkTsrNtvz0XdgR1kB5wxfyMo58Tmm2C2FNEX34Z-btJstgzDmJ3V6QQqY3A2tAbKoWAnMOmKr-JB6yVJXXp2_C2SWD8L1ggEEXD-Qs5_7iauZ4hf7BGo1-Mwd-08ayUUIZtxsR4ImbGA1bcm43hAe_AGu0MHu_ha4C58JFx3bS4p-uuMoXgjdA8GOlaTjAKQslbcWAd8pD2husJb6meA6NnAqDuiz0cVztRoeD20kTm3Ea34faPa0E5RKkkn-vRWXk-pltCILB6adF2IflDjufgGbmod7Ms5xaMdyr9YKDhEPQdRm9c4NuIT6s51Vyg7OVRap0i-moU357kJnBgnbAsOYMxV7dmsvIQHSef9pxKKyOykGk6-vyDQ65IMmupMhaUfdHIqii-fFPow'

Сервис Multiapp -> Фронтенд Ключа: В ответ Сервис Multiapp возвращает JSON, содержащий code.

Пример ответа
{
    "data": "eed766be-c30d-45aa-91a9-a986fb299741"
}

Затем Фронтенд Ключа выполняет редирект браузера Пользователя на зарегистрированный URL партнёра, добавляя code (и опционально mode=dark, если на клиенте включена тёмная тема) в query-параметры.

Пример URL редиректа: https://partner.com/app?code=eed766be-c30d-45aa-91a9-a986fb299741&mode=dark

Примечание

Здесь и далее в примерах CURL указаны hostname продуктивного контура keyapis.key.rt.ru для тестового контура необходимо использовать адрес keyapis-staging.k8s.key.rt.ru.

Шаг 2. Проверка авторизации на стороне партнёра

Фронтенд партнёра, получив запрос с параметром code, сначала проверяет, авторизован ли Пользователь уже в системе партнёра (например, по наличию валидной сессионной cookie или токена в local storage):

  • Если пользователь уже авторизован локально: Возможны два варианта (см. диаграмму): либо сразу перенаправить к функционалу, либо выполнить шаги 3-6 для синхронизации данных (если это первый вход через Ключ или требуется актуализация).
  • Если пользователь не авторизован локально: Фронтенд партнёра передаёт полученный code на Бэкенд партнёра для выполнения процедуры авторизации через Ключ.

Шаг 3. Запрос идентификационного токена

Бэкенд партнёра, получив code от Фронтенда партнёра, выполняет запрос к Бэкенду Ключа (Multiapp) для обмена code на id_token.

Бэкенд партнёра -> Сервис Multiapp: GET /multiapp/api/v1/partner/token (1)

  1. 📘 Подробнее см.:
    Swagger UI
    OpenAPI документация

Query-параметры запроса:

  • code: Значение кода авторизации, полученное на Шаге 1 и переданное через редирект.
  • response_type: Тип запрашиваемого ответа. Должен быть id_token.
  • nonce: Уникальный одноразовый идентификатор запроса (GUID), сгенерированный Бэкендом партнёра. Он будет включен в id_token и должен быть проверен на Шаге 5 для защиты от атак повторного воспроизведения.
Пример запроса
1
2
3
curl --location 'keyapis.key.rt.ru/multiapp/api/v1/partner/token?code=5fad897c-5bf3-4507-844b-298dfbd12cb7&response_type=id_token&nonce=f714a121-26b9-45fe-bd6e-cdb5b2c8d5c1' \
--header 'X-Request-Id: d61aef76-4770-4274-a48c-bc971cfaaadf' \
--header 'Accept: application/json'

Сервис Multiapp -> Бэкенд партнёра: в ответ Сервис Multiapp возвращает JSON, содержащий id_token. Этот id_token представляет собой Nested JWT (JWT, зашифрованный с помощью JWE). Шифрование выполняется с использованием публичного ключа партнёра, зарегистрированного на Шаге пререквизитов.

Пример ответа
{
    "data": {
        "id_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJ0eXAiOiJKV1QifQ.n2PLwdqz-Ool1BlsHIRMmCdeO8Qvc6F5Y_-kPE8KNB2CKBGIPZ2LSTowvvB1JhcKV5lMb1X0T-RyhUdoCP7eztkDQUGJ56cSqIAQ-NNkK-vMec4VEhrMrSXqqL6d3LhcTzyBbf9aSXASNyOX_Y2zi6bPN70jD7-yy77YkzxgU4ZEcLS_sDkqiD8vjh-xO6EMJKisz5Xv9vW0haFiNYuYlM5GswpDEnqOTWSjM3QG31veVkfNny__oIv_eUVLinJZuM39Dt25nssRZnjNsALI5Dw6Tn0_3WvBPXfoNjdSoLrS86dLAwJMv1BiSnkJL_Ty4sSGdDeVp9PfxZV454BKjw.cI5Q08JA7yU4-fn4qSqBXw.VSTJv1yvwcY5J-w6BgQKCoDXxV_xeiq87LnXsvnAV6B9a4Cp9fgABMvXfjcnxwUbdfvKOOD4_mzQQNYGZHj5lDq3GL2x9izPfCvdauLEWXZ01kpF9AWPX-c_20Pb3DfR-xWNdnZujDWcTZPsaIDHxbZboTIT2Sl911aSQKL265TXxEV0_fI8pY1eaGTTrTg9miEZU2mVCNpNyq7bljc_IrtC9bGVfqT-YdK1rYR4K7gg2NSb_DXUfTFVoZdbrh5RhEHulcu8MaARjDMuX9RyEGAKFbHlshDvIc4H3jFndpOg9pYfg78CQsfzD0oGHVAaZ7OcbIywz1cNEPNL5gmF7nKdaZbyS3depJJRZRcif1hJlNNZQ37QM_4wjfbZg_xCi7lcvjUz2CH5kPV6f6HKLOcsWieXXj1xUUcoI0SQaY1ehqhk7b2v88Nnf3RJpvGDUZ0r9QJiU9rD05btNmOgAeEl57-ydC5GS8RP76wRQxMRMRx3kWDqbOk6vM1wck2sVHMdTaiIjuKS7nRjCys1cVXiQB56GSMS5Y-ThTVYeiO4OuQqFDg2D4LyvstwRnr4t8zw8zqXN5eaIvZKU3_YuDZixsG9lohSrNoU3UHIVs1CDyBaBdqoCcRAkU28emN-OrXxCEAYL3jafsEnJ0UbMNrMKHsuVEAYA9ckChrEFDk78syvnDfvEgsid0HuqZqT0XeFZ5j29fvGB90cRwEoWIhoUo-rb8_VPlAAh_jIjLnObzepJCzpBZKy9_rDQidX_V49x_oEsMDn56p_o5ddg3Ax3MWcFxB-wEN_3EK-pBetYjhaZKyw1clHKVhC5Qy15HfxetqIHQ9aJqPsMA_i61suXErDVXW1pQHi0r_CrzVgY__EjVrS2fQy7RBX3AitrVsCWsuWnfOrAoUoimbIcdbuIECrrbRXt6bUSGF7HZClD1fQtG-BOgGNvxL80fiXqQrc14JdnVbW3yHbr0FOWJp0YlAsONkPydD9zLHMdoFfrw1Gt1k-AKFEG8MII6hDkxLRBjKs2qGDod33i0hvHi8xAM3bRL6yOQpjW6kbv7Wbo0ntVnOue_wVLzc62ZE41uGoXIfmFIebTNxHFVikRaBzVFu5ByeaxEEgAu9dfusG6dikIC7d6eS7fyfY3AREwlxw6PAca4X8YlpJqIKj5qDKvcVbv5WUFbyP4LpV7uYIYhZQ0NQ-rqC4sr6nSgw1IN3B7o1JPPbxiSSuXC-XHA.UGPBREteV9T19CtGhTh5ZVlxIS0hALetddkFyQqaVEQ",
        "access_token": "",   // Не используется в данном сценарии
        "token_type": "",     // Не используется
        "refresh_token": "",  // Не используется
        "expires_in": 0       // Не используется
    }
}

Шаг 4. Расшифровка токена (JWE -> JWT)

Бэкенд партнёра использует свой приватный ключ (сгенерированный на шаге пререквизитов и сохранённый локально) для расшифровки полученного id_token (JWE).

Результатом расшифровки является JWT (вложенный токен), содержащий информацию о пользователе.

Пример дешифрации JWE вручную

Для дешифрации JWE можно использовать онлайн-инструменты (например, https://dinochiesa.github.io/jwt/) или специализированные библиотеки для вашего языка программирования.

Входные данные:

  1. JWE строка (id_token из ответа на Шаге 3).
  2. Приватный ключ партнёра в формате PEM.
Пример приватного ключа партнёра (PEM)
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHK2M/7SZxTxzK
5XWL2LhVIAbpm6sypd2TSGTCs3v2kF9FfQi3cCbIfpGh2QG4EjaJOVaTtoDNsnk5
bz5G9/FU7VeZhVJxu+2bjN+kjDNPvPqCUxLJafKQGHtn1bcUCqxGSe0Sjh5lBnXQ
NHv+tubyo8IUk16Y7MNP36kzNVAARYBLTs/GjRYaIO9hqLnoYy+U72rUHEdGBh2h
qOw/4Cwa4ChSh2GJthcLex2HKxt68enWB1nbRjqVlPZbXBG8A8bL4tmCERDsmaMr
qhBMGLSWNZ/EY1mDSrU0Ny32NxwwlQV3PERsBFVE3XldlO1BTSklLQa31zB0ep3G
BiRqbloNAgMBAAECggEAaukMVqVLDGmmD/eJ2G6VzAPsEfvgssg8HzIt/CFyOlEX
AuryAd2uw9LFA7bD6HXGVnSz14iQrVnPHphrOpcfMTJR4nOhZLG3AtdkuSQU6wmL
Aufq3rkDXnv1yi/7HUP5Y6WNH3ewMxR7qy5xHLI7WCW1KWxNzBLTaI2renR0pB+3
P3lmJTM/I0F8zUGWLrywecu2AXYZbu/dx4LvoKoYT9IPplXVobXQe0V/pbWTGnY8
elOwhzYIjNri6L89oEVr691pc90i4Ti9Gxs1w438FR5ftH9wJTFMFgSYnsdwk/Yl
eL4DyFnrF07JAMZJOZYjDIV256mcXQQVP7xplehSQQKBgQD2ULWtkyr8OurJdr0K
PszVGFmQ6lKT5oPd0i2pB91tyRqFHmxPb+yyRa+LrUcIrBJiuevMcAGsLM6PZsi+
AtK3O9kAMCfW6Q7rs8lXP8DwRV4GMVa0xjUHjabWPe5KnaYoTNzbcXhSE8eExsEc
T7sK/4qxOy0TL7V+9l/LoqCCUQKBgQDPACGeJ2jaCoUj6TrDTQf9mT+VQLnsvOpB
VCFF+PE/4Ycd5jkYnBgK9Vu26WdtCUTnsbkSqbGVQ9zPWtipBX+m1/Kyv97b0R1T
TPDZisMMTL/IiMXrl0FYkneF8OoGY1QXknP8XaQU8FmQfTAtPzUKGAls0PrrMMiv
JzIFQICQ/QKBgBJkyIxjtwwK4kmVIlzHf8hR/y5BMVV2G1bPQTWkLrbqE57pPLFZ
7FULuyY1FF7jaqQQujkUDCvtVKeEx2zZDR89yuCmt/LmiIS1ck2fpyrKI1FXEeCC
BKfBvjm1ejv8NdJAmyWP/aoza+zQYs9YbOFPX+4IOz95ipYmk9TDGjVhAoGBAIsB
wZGdUXIq4mHNO6LvVdBdWy91eeWar40TXAzLMeb+ImhXtDvshtDFF8PrVYMJWHZn
CKuZvSFJozyz+w72HFUEa3zSEGx5L0JDRvIvlu/pkliuUIr21fEO2qjdsap8hXoW
5UCx+X/+L4cyKmoYKhwPLDUg2X+bEQlePt78A9pZAoGBALUgJYC7UlCUpyO60tWr
odfhcyrD1oUn6V8xxW2VTmlp2QYbNeiMWPb78OYnWUqjVJRRhYbmIqICXXP7iHgb
9aJ0PMz3y2jQcXz7y+gHm16klFGXLD2dR9G+vq3aEbMb02S2OGuwbztYKYDm1d+1
Ap60dvfe6TngUDriWH0KIj2O
-----END PRIVATE KEY-----

Результат (расшифрованный JWT):

Пример JWT после дешифрации
eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmY2Y3Yzk3LWUzMDItNGEyMy1hMWQ1LTNlN2IwYmUwNzk2OSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJlMzY0ZjU0Ny0zYzZjLTQwMzQtOWFkMS1jNTQyNjRhZmE5NjYiLCJub25jZSI6IjA2NDAwZjAyLWIyOTUtNDY5ZC1hMDkzLTE0YTg3YjlkMGI4NyIsInN1YiI6IjIyNDk3MyIsInJvbGUiOiJtYXN0ZXIiLCJwaG9uZV9udW1iZXIiOiIrNzkwMDQxMzAwMTgiLCJvcnBvbiI6IjIxNjY0NDYiLCJyb29tIjoiMSIsImNvbXBhbnlfaWQiOiIyMTYiLCJjb21wYW55X25hbWUiOiLQo9CaINCf0LXRgNCy0YvQuSDRgNCw0YHRgdCy0LXRgiIsImp0aSI6IjczZDJjNzM0LTczYzEtNDRhNi04YTVlLWU1NzU0Mzg0MGU5ZSIsImV4cCI6MTc0NDI4NzEzMywiaWF0IjoxNzEzMjQ1OTI4LCJpc3MiOiJodHRwczovL2p3a3Mua2V5LnJ0LnJ1In0.II6tHXJFQ6rs66davVI4IOCdm2NkXv2OfI256mWHHPDtynmzgcN6mWrrhcC6ea0HD5_YL2OHCTFmA24s7rEKEzkOZyk-9cu3cBkmz5OlZvhU5CGjj1lx5Aalc4fDlzPYxRIMGDR4a5eZM9Y6oBCB5YpAqoPgZQo3uT_Cf1zqQ7IbfcQocqWLt67HLmXn5mo-Jm1l9GPWtfp9QTagUVilzqLw_tdH6SmKPazDAI9Yf1VUjaS4sfPnu3Ozr7B_WmRZuWMgE5tOGVgIXx3W3le5zKnQo5BzX9Sebftx7b89blNfniWLZR2tDssMfw-DjzGNdMCZQZGiJyNUA9ElZgvd2w

Содержимое (Payload) этого JWT можно посмотреть, например, на https://jwt.io/.

Состав получаемого идентификационного JWT токена пользователя:

Header:

{
    "alg": "RS256",                                      # Алгоритм подписи
    "kid": "dfcf7c97-e302-4a23-a1d5-3e7b0be07969",       # ID ключа, используется при определении публичного ключа, которым подписан токен
    "typ": "JWT"                                         # Тип токена
}

Payload:

{
    "aud": "e364f547-3c6c-4034-9ad1-c54264afa966",       # ID партнёра на платформе Ключ      
    "nonce": "06400f02-b295-469d-a093-14a87b9d0b87",     # Ваучер на выпуск токена сгенерированный партнёром
    "sub": "224973",                                     # Идентификатор пользователя на платформе Ключ  
    "role": "master",                                    # Роль пользователя на платформе Ключ
    "phone_number": "+79004130018",                      # Опционально. Телефонный номер пользователя
    "orpon": "2166446",                                  # Опционально. ОРПОН идентификатор дома пользователя 
    "room": "1",                                         # Опционально. Номер квартиры пользователя
    "company_id": "216",                                 # Опционально. Идентификатор управляющей компании к которой относится пользователь на платформе Ключ
    "company_name": "УК Первый рассвет",                 # Опционально. Наименование управляющей компании
    "jti": "73d2c734-73c1-44a6-8a5e-e57543840e9e",       # ID токена           
    "exp": 1744287133,                                   # Время истечения действия токена в формате NumericDate (UNIX timestamp в UTC, RFC 7519)
    "iat": 1713245928,                                   # Время выпуска токена в формате NumericDate (UNIX timestamp в UTC, RFC 7519)
    "iss": "https://jwks.key.rt.ru"                      # URL адрес "издателя" токена 
}

Шаг 5. Валидация токена (JWT)

После расшифровки JWE Бэкенд партнёра должен валидировать полученный JWT.

Процесс валидации

1. Получение публичных ключей Ключа (JWKS)
  • Бэкенд партнёра запрашивает актуальный набор публичных ключей (JWKS) у Сервиса JWKS платформы "Ключ". Запрос выполняется только если нужного ключа нет в кэше или кэш истёк (см. раздел "Кэширование JWKS"):

    Бэкенд партнёра -> Сервис JWKS: GET jwks/api/v1/key/.well-known/jwks.json (1)

    1. 📘 Подробнее см.:
      Swagger UI
      OpenAPI документация
      Этот запрос возвращает список публичных ключей Ключа в формате JWKS. Рекомендуется кэшировать этот ответ (см. раздел "Кэширование публичных JWK-ключей").
Пример запроса
1
2
3
curl --location 'http://keyapis.key.rt.ru/jwks/api/v1/key/.well-known/jwks.json' \
--header 'X-Request-Id: 7d33bbe1-64c0-4f60-b037-5bd59bb0cf42' \
--header 'Accept: application/json'
  • Сервис JWKS -> Бэкенд партнёра: метод возвращает три последних публичных ключа, пример ответа:
Пример ответа
{
    "keys": [
        {
            "use": "sig",  // Использование ключа: подпись (signature)
            "kty": "RSA",  // Тип ключа: RSA
            "kid": "dfcf7c97-e302-4a23-a1d5-3e7b0be07969", // ID ключа
            "alg": "RS256", // Алгоритм, используемый с этим ключом
            "n": "mE+CyHBk2e02vsFtkdq/VWpfNZ2xFKqUdFTLxsgEeA7J2zXVvw6mAWdKd22Lm49ezazuV4wr9gC3ZGQwFyQs0BV7PT0/U7NCz5Hlz/7IS9NI+ximQdzZq9HGbE0gx9pgOxyiJ8lOVLxzTd1yU+pQ5MKNh0yEzplPrq6j8tAd3Zc52BFCHkVTKN8M03CoaShJOqYymd9xXiqRfFt7fm7xIyv3P5gzVAgLmI1Gh5GQTT9sPWYwYOIDR0M1NeCuCSVnyZa8GLuJu0KmCGFnHhlBztIvwYYXryp02rfWBBcmABruPSa9dvFq2cyv7cpQvc5JM1hp/+ZNmjmZzSbxHmzuhQ==", // Модуль RSA
            "e": "AQAB", // Экспонента RSA
            "x5c": [
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmE+CyHBk2e02vsFtkdq/VWpfNZ2xFKqUdFTLxsgEeA7J2zXVvw6mAWdKd22Lm49ezazuV4wr9gC3ZGQwFyQs0BV7PT0/U7NCz5Hlz/7IS9NI+ximQdzZq9HGbE0gx9pgOxyiJ8lOVLxzTd1yU+pQ5MKNh0yEzplPrq6j8tAd3Zc52BFCHkVTKN8M03CoaShJOqYymd9xXiqRfFt7fm7xIyv3P5gzVAgLmI1Gh5GQTT9sPWYwYOIDR0M1NeCuCSVnyZa8GLuJu0KmCGFnHhlBztIvwYYXryp02rfWBBcmABruPSa9dvFq2cyv7cpQvc5JM1hp/+ZNmjmZzSbxHmzuhQIDAQAB"
            ] /* опционально: сертификат X.509 */
        },
        {
            "use": "sig",
            "kty": "RSA",
            "kid": "c4e5cd99-180d-4261-b65a-95ed956279ee",
            "alg": "RS256",
            "n": "6/OiiQR9z7bCyMolXNQvNXtaTtTdU8EhMU/jjafiTGKm8ZH1ucGUDGjQL/hvt5E218fnyUhZRh7y7vv9KVhPrhigZdEEtvQxbWHzgd3VvXIbw1lk0cKTEtg2Non1Yp8Ny+phXLN35xVnHTEn+/diEBGydAKezYUZqeKo3vm4lIdUGnEMAOkVsntKuNv2n2oz2ZCZQbpugsvha4bG0TNCUeVL45ZUA2dDR9+tjEOqoGEmZrjbiFxY4pxicoKo3TFBT9i3f4CzrbzYTeUviW7/E/LlD+Ee5/dVmByiuKcCauX2KoHfM8GmilkAY4PJJcXbUO68/TDp3A2vzc7WO3h4eQ==",
            "e": "AQAB",
            "x5c": [
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6/OiiQR9z7bCyMolXNQvNXtaTtTdU8EhMU/jjafiTGKm8ZH1ucGUDGjQL/hvt5E218fnyUhZRh7y7vv9KVhPrhigZdEEtvQxbWHzgd3VvXIbw1lk0cKTEtg2Non1Yp8Ny+phXLN35xVnHTEn+/diEBGydAKezYUZqeKo3vm4lIdUGnEMAOkVsntKuNv2n2oz2ZCZQbpugsvha4bG0TNCUeVL45ZUA2dDR9+tjEOqoGEmZrjbiFxY4pxicoKo3TFBT9i3f4CzrbzYTeUviW7/E/LlD+Ee5/dVmByiuKcCauX2KoHfM8GmilkAY4PJJcXbUO68/TDp3A2vzc7WO3h4eQIDAQAB"
            ]
        },
        {
            "use": "sig",
            "kty": "RSA",
            "kid": "49b1cf3e-160b-461e-9acb-f8e0c78e4023",
            "alg": "RS256",
            "n": "u6GAzWzYcM7HIFkfYcEOksW8vun+qwlYh1XGdryi01M1SgUd8VccJ/9cpRY9CHVAcDxoI7wwSuF95DqHUNQ41Fq8C3rRYdrUDNneqVB8uk1Z+aJZsfb2+I+oKuL1+2zQsjW6X/1D87O/n0NLsInL4Gi/cAzSH8MU4bbHBwjLQPu895x4rANfP2TQ1PFZq4UlQpOyZxrvEJ1/qCLLhfMy0bsO/VtsC4tpMWWWgefdnjrEG1DuS1ru7j9h6dzoY3Vm4WgzOwl4rhsul73uwpf4tn5NzHwS+h1I5SAfYxHTyTN9t4cB44UKxX5E6hs6O9HiZpDVJlabm85Xz1/ED6DSTw==",
            "e": "AQAB",
            "x5c": [
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6GAzWzYcM7HIFkfYcEOksW8vun+qwlYh1XGdryi01M1SgUd8VccJ/9cpRY9CHVAcDxoI7wwSuF95DqHUNQ41Fq8C3rRYdrUDNneqVB8uk1Z+aJZsfb2+I+oKuL1+2zQsjW6X/1D87O/n0NLsInL4Gi/cAzSH8MU4bbHBwjLQPu895x4rANfP2TQ1PFZq4UlQpOyZxrvEJ1/qCLLhfMy0bsO/VtsC4tpMWWWgefdnjrEG1DuS1ru7j9h6dzoY3Vm4WgzOwl4rhsul73uwpf4tn5NzHwS+h1I5SAfYxHTyTN9t4cB44UKxX5E6hs6O9HiZpDVJlabm85Xz1/ED6DSTwIDAQAB"
            ]
        }
    ]
}
2. Проверка подписи JWT
  • Извлечь kid из заголовка полученного JWT (см. пример JWT в Шаге 4).
  • Найти в полученном/кэшированном JWKS публичный ключ с соответствующим kid.
  • Используя этот публичный ключ, проверить подпись JWT.

Примечание

Для проверки валидности JWT-токена рекомендуем воспользоваться одной из стандартных библиотек из списка: https://jwt.io/libraries и по возможности воздержаться от ручной реализации данных проверок.

Пример проверки подписи JWK вручную

Для проверки подписи используем ключ с kid указанным в заголовке идентификационного токена - dfcf7c97-e302-4a23-a1d5-3e7b0be07969

Т.к. мы проверяем вручную нам необходимо JWK конвертировать в PEM, переходим на сайт: https://8gwifi.org/jwkconvertfunctions.jsp

В поле input вставляем JWK без параметра x5c:

Пример JWK
{
  "use": "sig",
  "kty": "RSA",
  "kid": "dfcf7c97-e302-4a23-a1d5-3e7b0be07969",
  "alg": "RS256",
  "n": "mE+CyHBk2e02vsFtkdq/VWpfNZ2xFKqUdFTLxsgEeA7J2zXVvw6mAWdKd22Lm49ezazuV4wr9gC3ZGQwFyQs0BV7PT0/U7NCz5Hlz/7IS9NI+ximQdzZq9HGbE0gx9pgOxyiJ8lOVLxzTd1yU+pQ5MKNh0yEzplPrq6j8tAd3Zc52BFCHkVTKN8M03CoaShJOqYymd9xXiqRfFt7fm7xIyv3P5gzVAgLmI1Gh5GQTT9sPWYwYOIDR0M1NeCuCSVnyZa8GLuJu0KmCGFnHhlBztIvwYYXryp02rfWBBcmABruPSa9dvFq2cyv7cpQvc5JM1hp/+ZNmjmZzSbxHmzuhQ==",
  "e": "AQAB"
}

Получаем ключ в формате PEM:

Пример ключа в формате PEM
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmE+CyHBk2e02vsFtkdq/
VWpfNZ2xFKqUdFTLxsgEeA7J2zXVvw6mAWdKd22Lm49ezazuV4wr9gC3ZGQwFyQs
0BV7PT0/U7NCz5Hlz/7IS9NI+ximQdzZq9HGbE0gx9pgOxyiJ8lOVLxzTd1yU+pQ
5MKNh0yEzplPrq6j8tAd3Zc52BFCHkVTKN8M03CoaShJOqYymd9xXiqRfFt7fm7x
Iyv3P5gzVAgLmI1Gh5GQTT9sPWYwYOIDR0M1NeCuCSVnyZa8GLuJu0KmCGFnHhlB
ztIvwYYXryp02rfWBBcmABruPSa9dvFq2cyv7cpQvc5JM1hp/+ZNmjmZzSbxHmzu
hQIDAQAB
-----END PUBLIC KEY-----

На сайте jwt.io подставляем его в инпут public key в разделе verify signature, видим что подсветилась надпись signature verified.

3. Проверка claims JWT
  • iss (Issuer): Значение должно соответствовать ожидаемому издателю токенов Ключа (например, https://jwks.key.rt.ru). Значение должно совпадать с тем, которое указано в документации или конфигурации.
  • exp (Expiration Time): Время истечения срока действия токена (Unix timestamp) находится в будущем.
  • iat (Issued At): Время выпуска токена (Unix timestamp) должно находится в прошлом.
  • nonce: Обязательно убедиться, что значение поля nonce в payload JWT совпадает с тем nonce, который Бэкенд партнёра сгенерировал и отправил в запросе на Шаге 3. Это предотвращает атаки повторного воспроизведения.

Только в случае, если все проверки прошли успешно, получатель может десериализовать JSON payload JWT токена.

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

Кэширование публичных JWK-ключей

Допускается кэширование JWK-ключей, используемых для валидации JWT-токенов, на Бэкенде партнёра. Время жизни такого кэша необходимо устанавливать таким образом, чтобы это не приводило к ошибкам валидации токенов в случае ротации JWK-ключей на стороне проекта "Ключ" (лучше установить 1 час). В случае кэширования JWK-ключей, необходимо реализовать следующие сценарии проверок.

Сценарий А: Успешная валидация токена кэшированными JWK-ключами.

  1. Приходит запрос с токеном, чей kid найден среди кэшированных JWK-ключей, и время жизни кэша не истекло.
  2. Валидация подписи токена завершается успешно.
  3. Токен считается валидным.

Сценарий B: Неуспешная валидация токена кэшированными JWK-ключами.

  1. Приходит запрос с токеном, чей kid найден среди кэшированных JWK-ключей, и время жизни кэша не истекло.
  2. Валидация подписи токена завершается неуспехом.
  3. Токен считается невалидным, запрос отклоняется.

Сценарий C: Валидация токена со сбросом кэша JWK-ключей.

  1. Приходит запрос с токеном, чей kid не найден среди кэшированных JWK-ключей или время жизни кэша истекло.
  2. Партнёр должен сбросить кэш JWK-ключей и запросить весь список активных JWK-ключей заново с соответствующего окружению адреса.
  3. Валидация продолжается по сценарию А или B.

Общее количество итераций по сценариям не должно быть больше двух.

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

Шаг 6. Обработка и синхронизация данных пользователя на стороне партнёра

При успешном получении и валидации id_token (шаги 1-5 выполнены), Бэкенд партнёра получает актуальные данные о пользователе из платформы Ключ. Далее необходимо идентифицировать этого пользователя в локальной системе партнёра, авторизовать его и обеспечить консистентность данных между системами.

Идентификация пользователя:

Партнёр должен попытаться найти пользователя в своей базе данных, используя поля из id_token в следующем приоритетном порядке:

  1. sub (уникальный идентификатор пользователя в Ключе)
  2. Связка orpon + room (идентификатор квартиры)
  3. phone_number
  4. email

Сценарии обработки в зависимости от результата поиска:

  1. Пользователь найден по sub:

    • Действие: Пользователь идентифицирован. Авторизуйте пользователя в своей системе (если он еще не авторизован).
    • Синхронизация: Сравните остальные поля из id_token (phone_number, email, orpon, room, role и т.д.) с данными в локальном профиле. Если обнаружены расхождения, обновите локальные данные на актуальные значения из id_token.
  2. Пользователь по sub не найден, но найден по orpon + room:

    • sub из id_token не совпадает с sub, ранее связанным с этой квартирой у партнёра (если такая связь была). Это может означать либо другого пользователя из той же квартиры, либо того же пользователя с пересозданной учётной записью в Ключе.

    Примечание: возможность привязки нескольких пользователей к одной квартире появится при дальнейшем развитии платформы "Ключ".

    • Действия партнёра (требуют учёта внутренней бизнес-логики):
      • Попытка уточнения: Проверьте, совпадают ли phone_number или email из нового id_token с контактными данными существующего локального пользователя, найденного по orpon + room.
      • Если контактные данные совпадают (вероятно, тот же пользователь): Обновите sub в существующей локальной учётной записи на новый sub из id_token. Затем выполните синхронизацию остальных полей, как в пункте 1. Авторизуйте пользователя.
      • Если контактные данные НЕ совпадают (вероятно, другой пользователь или недостаточно данных для сопоставления): Бизнес-логика партнёра определяет дальнейшие шаги. Рекомендуемый подход (если система партнёра это позволяет) — создать новую учётную запись для пользователя с данными из id_token (включая новый sub). Авторизуйте этого нового пользователя. Если создание новой учётной записи для той же квартиры нежелательно, партнёр должен определить альтернативную логику (например, запросить дополнительные данные у пользователя).
  3. Пользователь не найден по sub и orpon + room, но найден по phone_number или email:

    • Вероятнее всего, это тот же пользователь, который либо пересоздал учётную запись в Ключе (получил новый sub), либо сменил место жительства (orpon + room), сохранив контактные данные.
    • Действие: Считаем, что пользователь идентифицирован.
    • Синхронизация: Необходимо обновить sub в найденной локальной учётной записи на новый sub из id_token. Также обновите все остальные релевантные поля (orpon, room, phone_number, email, role и т.д.) на актуальные значения из id_token. Авторизуйте пользователя.
  4. Пользователь не найден ни по одному из ключевых полей:

    • Это новый пользователь для системы партнёра.
    • Действие: Создайте новую учётную запись пользователя на основе всех данных из id_token (sub, orpon, room, phone_number, email, role и т.д.).
    • Авторизация: Авторизуйте созданного пользователя в своей системе (например, выдав собственный сессионный токен/cookie).

Общие рекомендации

  • После первоначальной идентификации и связывания, sub должен стать основным идентификатором для связи локальной учётной записи с пользователем платформы Ключ.
  • При каждом успешном входе через Ключ рекомендуется актуализировать локальные данные пользователя на основе информации из id_token.

Примечание

Рекомендуем сохранять все пользовательские данные, полученные из id_token платформы Ключ (даже если они не используются напрямую в текущей логике). В дальнейшем они могут понадобиться для реализации дополнительных сценариев интеграционного взаимодействия, например, для отправки push-уведомлений или расширенной аналитики.


Требования к безопасности

  • Хранение приватного ключа: Приватный ключ, сгенерированный партнёром с помощью key-cli (Шаг 4 Пререквизитов), является секретным. Он должен храниться в безопасном месте на бэкенде партнёра (например, в защищённом хранилище секретов, не в системе контроля версий). Компрометация этого ключа позволит злоумышленнику расшифровывать токены, предназначенные для партнёра.
  • Хранение API ключа: API ключ, полученный от Менеджера (Шаг 3 Пререквизитов), используется для аутентификации в key-cli. Его также следует хранить безопасно и использовать только для первоначальной настройки и, возможно, перевыпуска ключей шифрования.
  • Валидация токенов: Строго следуйте всем шагам валидации JWT, описанным в Шаге 5, включая проверку подписи, iss, aud, exp, iat и nonce.
  • HTTPS: Всё взаимодействие между браузером пользователя, фронтендом партнёра и бэкендом партнёра, а также между бэкендом партнёра и API Ключа должно происходить по HTTPS.
  • Защита от CSRF: Если взаимодействие Фронтенда партнёра и Бэкенда партнёра происходит через веб-интерфейс, убедитесь в наличии защиты от CSRF-атак при передаче code на бэкенд.

Обработка ошибок

В процессе взаимодействия возможны ошибки на различных этапах. Партнёру следует реализовать корректную обработку следующих ситуаций:

  • Сетевые ошибки: Недоступность API Ключа (/token, /jwks.json).
  • Ошибки API Ключа:
  • Невалидный или истёкший code при запросе /token (Ключ вернёт ошибку HTTP 400).
  • Неверный response_type или отсутствующий nonce.
  • Внутренние ошибки сервера Ключа (HTTP 500).
  • Ошибки на стороне партнёра:
  • Невозможность расшифровать JWE (например, из-за неверного приватного ключа).
  • Неуспешная валидация JWT (неверная подпись, истёкший срок, несовпадение aud, iss или nonce).
  • Ошибки при поиске/создании/обновлении пользователя в базе данных партнёра.

Отправка пуш-уведомлений

Платформа "Ключ" предоставляет партнёрам возможность отправлять push-уведомления своим пользователям через API сервиса Multiapp. Это позволяет информировать пользователей о важных событиях, обновлениях или персонализированных предложениях непосредственно в мобильном приложении "Ключ" или PWA (https://key.rt.ru) платформы.

При получении push-уведомления пользователь может нажать на него — в этом случае произойдёт переход непосредственно в приложение партнёра, отправившего уведомление.

Все отправленные партнёрами уведомления доступны пользователю для просмотра в приложении "Ключ" в разделе "Уведомления", даже если пользователь не взаимодействовал с push напрямую.

Внешний вид раздела "Уведомления

Пререквизиты для отправки уведомлений

Для отправки уведомлений Партнёру потребуются:

  1. API ключ партнёра: Тот же ключ, что используется для key-cli и был получен от Менеджера (см. Шаг 3 Пререквизитов). Этот ключ используется для аутентификации запросов на отправку уведомлений.
  2. Идентификаторы приложений партнёра (appId): Получаются от Менеджера (см. Шаг 3 Пререквизитов). Уведомление отправляется в контексте конкретного приложения партнёра.
  3. Идентификаторы шаблонов уведомлений (templateId): Получить список доступных шаблонов и их идентификаторы можно у Менеджера или самостоятельно, используя метод API
    GET /multiapp/api/v1/notification_template/list (1)

    1. 📘 Подробнее см.:
      Swagger UI
      OpenAPI документация

Заголовки запроса

  • X-Api-Key (обязательный): API ключ партнёра.
  • X-Request-Id (рекомендуемый): Уникальный идентификатор запроса (UUID), генерируемый клиентом.
Пример запроса получения списка доступных шаблонов
  curl --location --request GET 'https://keyapis.key.rt.ru/multiapp/api/v1/notification_template/list' \
 --header 'X-Api-Key: ВАШ_API_КЛЮЧ' \
 --header 'X-Request-Id: cb2c603a-29e4-448d-9c8f-1b4f9d0849c5' \
 --header 'Accept: application/json'

В ответе придёт список шаблонов с их идентификаторами, названиями и текстами.

Пример ответа на запрос получения списка доступных шаблонов
[
    {
        "result": {
            "data": {
                "id": "11fdcc81-13d3-469d-95a1-d9a8d6d5a1d8",
                "templateHeader": "Оплатите в срок до {{pay_date|date}}",
                "templateText": "Оплатите задолженность {{sum|number}} за период с {{interval|number}} в срок до {{pay_date|date}}.",
                "createdAt": "2025-05-22T08:33:07.256825Z",
                "changedAt": "2025-05-26T04:15:36.462283Z",
                "deletedAt": null,
                "title": "Demo"
            }
        }
    }
]

Mustache теги в шаблонах уведомлений

Партнёр может управлять содержимым текста уведомления только посредством указания значений для тегов в тексте шаблона уведомления.

Формат тега:
{{имя_переменной|тип_данных}}

  • имя_переменной — произвольное название переменной (например, pay_date, sum, interval), которое определяет, какое значение нужно подставить.
  • тип_данных — определяет тип значения, которое будет вставлено в шаблон. Поддерживаются следующие типы данных:
Тип данных Переменная Описание
1 Число NUMBER Строка длиной до 15 символов, допускаются только числовые символы и однократное использование знаков "+" или "-" (в начале) и "." или "," в качестве десятичного разделителя. Валидные варианты: "123", "-123", "+123", "123,2", "123.2"
2 Дата DATE Строка в формате dd.MM.yyyy, где dd, MM и yyyy — это только цифры
3 Телефон PHONE Строка в формате +X XXX XXX-XX-XX или X XXX XXX-XX-XX, где X — это только цифры
4 Время TIME Строка в формате HH:mm, где HH и mm — это только цифры

Сценарий отправки уведомления

Бэкенд партнёра -> Сервис Multiapp: POST /multiapp/api/v1/notification (1)

  1. 📘 Подробнее см.:
    Swagger UI
    OpenAPI документация

Аутентификация запроса производится с помощью HTTP-заголовка X-Api-Key, содержащего API ключ партнёра.

Заголовки запроса

  • X-Api-Key (обязательный): API ключ партнёра.
  • X-Request-Id (рекомендуемый): Уникальный идентификатор запроса (UUID), генерируемый клиентом.

Состав полей тела запроса (data):

Поле Обязательность Тип Описание
appId обязательный string, GUID Идентификатор вашего приложения в системе "Ключ", в контексте которого отправляется уведомление.
orpon обязательный string, int64 ОРПОН дома пользователя, которому предназначено уведомление.
roomNumbers обязательный массив строк Массив, содержащий номер(а) квартиры (помещений) пользователей. Можно указать один или несколько номеров квартир в рамках одного orpon для массовой отправки идентичного уведомления жителям этих квартир (на данный момент наша платформа поддерживает только одного жителя в квартире).
templateId обязательный string, GUID Идентификатор шаблона уведомления.
values обязательный объект в формате key-value Объект, содержащий значения для подстановки в Mustache-переменные, определённые в template_header и template_text указанного templateId. Ключи этого объекта должны соответствовать именам переменных, указанным в шаблоне.
payload необязательный объект в формате key-value Произвольные данные в формате "ключ-значение" (строки). Эта полезная нагрузка будет доставлена в ваше приложение при переходе в виде query параметров.
Пример запроса на отправку уведомления
curl --location --request POST 'https://keyapis.key.rt.ru/multiapp/api/v1/notification' \
--header 'X-Api-Key: ВАШ_API_КЛЮЧ' \
--header 'X-Request-Id: cb2c603a-29e4-448d-9c8f-1b4f9d0849c5' \
--header 'Content-Type: application/json' \
--data-raw '{
  "data": {
    "appId": "8940e063-7284-4d37-bef1-8762c9be64f3",
    "orpon": "2166446",
    "roomNumbers": ["1", "15"],
    "templateId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "values": {
      "sum": "2500,75",
      "interval": "12.2023",
      "pay_date": "25.01.2024"
    },
    "payload": {
      "targetView": "invoiceDetails",
      "invoiceId": "inv-2023-12-001"
    }
  }
}'

Ошибки:

Ошибка Описание
ValuesError Значения для подстановки не соответствуют шаблону уведомления.
AppBlockedError Приложение находится в статусе "Заблокировано".
AppAssignmentError Ошибка, связанная с назначениями приложения.
Причины:
- Приложение назначено не только по ОРПОНу или ОРПОНу и номеру квартиры;
- Приложение не назначено на переданные orpon и room_number
ValidationError Ошибка валидации
Пример ответа с ошибкой валидации (HTTP 400)
{
    "error": {
        "validation": {
            "path": "Data.Values",
            "message": "Значение обязательно для заполнения"
        }
    }
}

Ссылки