Вход через Ansible

Ansible даёт разработчикам сайтов и приложений бесплатную и открытую платформу — пользователь регистрируется и входит своей учётной записью Ansible, вместо того чтобы придумывать ещё один пароль.

Зачем это вам

  • Выше конверсия
    Вход в два касания — меньше тех, кто закрыл форму регистрации и не вернулся.

  • Не нужно хранить пароли
    Нечего хранить, нечего восстанавливать и нечему утекать: чужого секрета вы просто не видите.

  • Прямая связь
    С пользователем можно связаться прямо в Ansible — с обычными пуш-уведомлениями.

  • Что дальше
    Дальше — Bot API и мини-приложения: сервис целиком внутри мессенджера.

Заметка: Здесь описаны библиотека входа Ansible и новый — на базе OpenID Connect порядок входа.


С чего начать

У нас есть небольшой конструктор — он быстро соберёт кнопку входа для вашего интерфейса. Можно обратиться и напрямую к JS API библиотеки.

На мобильных используйте обычный OIDC-редирект через системный браузер: те же адреса, отдельный SDK не нужен.

Кроме того, Ansible поддерживает стандартный OpenID Connect , так что вход можно подключить любой совместимой библиотекой или брокером — Keycloak, Authentik, Auth0 и подобными.

Мы следуем обычному Authorization Code Flow с PKCE .

Если хочется разобраться в OIDC подробнее, начните с руководства OpenID Foundation.

TL;DR

  • Заведите приложение в кабинете разработчика — бот @ansible_dev_bot, кнопка меню открывает https://id.ansible.su/app.
  • Впишите адреса: origin страницы, где стоит кнопка, и redirect URI для OIDC. Оттуда же заберите client_id и client_secret.
  • Поставьте библиотеку https://id.ansible.su/widget.js на страницу — или сходите по обычному OIDC.
  • Проверьте id_token на своём сервере, прежде чем поверить тому, что прислал браузер.

Что-то не сходится — напишите нам, пожалуйста, с описанием шага, на котором встали.

Заводим приложение

Приложению нужна карточка: имя и логотип, которые пользователь увидит в окне подтверждения. Заводится она в кабинете разработчика — откройте бот @ansible_dev_bot и нажмите кнопку меню.

Логотип стоит взять тот же, что у вас на сайте, а имя — такое же, как в адресной строке. Пользователь соглашается отдать свои данные ровно тому, кого узнал: незнакомое имя в этом окне — причина нажать «Отмена».

Вход на example.com
Сайт получит ваши имя, ник и фото профиля.
Устройство iPhone 15 ProSafari 18
IP-адрес 203.0.113.24Москва
Попытка входа сделана с устройства выше.
Разрешить сообщения
Бот сайта сможет писать вам в Ansible.
Отмена Войти

client_id у нас — не идентификатор бота, а строка вида app_9f3ab21c, которую выдаёт кабинет. client_secret показывается там же и только владельцу приложения.

Разрешённые адреса

В карточке приложения два списка, и они про разное:

  • Origins — адреса страниц, откуда разрешено открывать окно входа, например https://example.com. Без порта и без пути.
  • Redirect URIs — точные адреса возврата для OIDC, например https://example.com/auth/callback. Сверяются побайтово: лишний слэш в конце — уже другой адрес.

Важно: вход возможен только с заранее вписанных адресов. Это не формальность: без такой проверки чужой сайт мог бы провести пользователя через настоящее окно подтверждения и забрать результат себе.

Библиотека входа

Настройте кнопку и заберите готовый код для своей страницы.

Ту же библиотеку можно вызывать из кода:

Методы

Библиотека кладёт в страницу один объект — Ansible.

МетодЧто делает
Ansible.login(clientId, callback)Открывает окно входа и вызывает callback с результатом. Оба аргумента необязательны: без них берутся значения из атрибутов тега <script>.

Отдельного шага инициализации нет — библиотека готова к работе сразу после загрузки.

Параметры

Настраивается атрибутами того же тега <script>.

АтрибутТипЧто значит
data-clientstringclient_id из кабинета. Если он задан, библиотека сама вставит кнопку на место тега.
data-onauthstringИмя глобальной функции, которую позвать с результатом. Именно имя: data-onauth="onAnsibleAuth", а не onAnsibleAuth(data) — выражение не выполнится.
data-labelstringНеобязательно. Надпись на кнопке.
<script async src="https://id.ansible.su/widget.js"
        data-client="app_9f3ab21c"
        data-label="Войти через Ansible"
        data-onauth="onAnsibleAuth"></script>

<script>
  function onAnsibleAuth(data) {
    // data.jwt приходит только first-party клиентам.
    // Всё остальное проверяйте на своём сервере.
    fetch('/auth/ansible', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
  }
</script>

Что приходит в ответ

Функция из data-onauth получает один объект.

ПолеТипЧто значит
statusstringconfirmed при успехе.
userobjectИдентификатор и имя вошедшего, плюс auth_date.
hashstringHMAC-SHA256 по полям auth_date, id, name, photo_url, ключ — SHA-256 от вашего client_secret. Проверяется на сервере.
jwtstringТолько для доверенных приложений. Обычным клиентам не выдаётся.

🚨 Подписью покрыты только перечисленные поля. Всё, что пришло рядом и в hash не входит, — это данные из браузера: проверить их нечем, и решения по ним принимать нельзя.

OpenID Connect

Если вы настраиваете готовую библиотеку или брокера, вот значения для её конфигурации.

Адрес discovery-документа

https://id.ansible.su/.well-known/openid-configuration

Настройки клиента

Параметр Значение
Client ID Client ID из кабинета разработчика
Client Secret Client Secret из кабинета разработчика
Response Type код
PKCE Обязателен (S256)

Какие scope есть

Мы объявляем два scope. openid обязателен.

ScopeЧто даётClaim’ы
openidОбязателен. Идентификатор пользователя и время входа.sub, iss, aud, iat, exp, auth_time
profileОтображаемое имя.name, picture

Номера телефона и адреса почты вход через Ansible не отдаёт — такого scope у нас нет. Если вам нужен подтверждённый номер, спросите его у пользователя сами: молчаливая передача чужого телефона стороннему сайту — не та цена, которую стоит платить за удобный вход.

picture сейчас приходит пустой строкой: фото профиля в токен пока не попадает. Поле объявлено и не исчезнет — рассчитывайте на ключ, но не на значение.

Что лежит в токене

Все данные приходят прямо в ID-токене. Раскодированный id_token выглядит так:

{
  "iss": "https://id.ansible.su",
  "aud": "app_9f3ab21c",
  "sub": "1234567890",
  "iat": 1753900000,
  "exp": 1753903600,
  "auth_time": 1753900000,
  "name": "Мария",
  "picture": "",
  "nonce": "n-0S6_WzA2Mj"
}

sub — строка, даже когда внутри число. Складывайте её в текстовое поле: разбор в целое переживёт не каждый идентификатор.

Эндпоинт /userinfo есть и отдаёт sub, name и picture по Bearer-токену, но ничего сверх того, что уже лежит в id_token. Библиотеке OIDC его можно не дёргать.

Без библиотеки

Если делаете OIDC руками, вот адреса и порядок шагов.

Адреса

  • Discovery: https://id.ansible.su/.well-known/openid-configuration
  • Authorization: https://id.ansible.su/authorize
  • Token: https://id.ansible.su/token
  • UserInfo: https://id.ansible.su/userinfo
  • Ключи (JWKS): https://id.ansible.su/.well-known/jwks.json

Настраиваете готовую библиотеку или брокера (Keycloak, Authentik, Auth0) — хватит одного адреса discovery, остальное он заберёт сам.

Шаг 1. Отправить пользователя

Отправьте пользователя в браузере на адрес авторизации.

GET https://id.ansible.su/authorize?
    client_id=<YOUR_CLIENT_ID>&
    redirect_uri=<YOUR_CALLBACK_URL>&
    response_type=code&
    scope=openid%20profile&
    state=<RANDOM_STRING>&
    code_challenge=<PKCE_CHALLENGE>&
    code_challenge_method=S256
  • client_id — строка app_… из кабинета.
  • redirect_uri — ровно тот, что вписан в карточке приложения.
  • state — случайная строка с вашего сервера, защита от CSRF.
  • code_challengebase64url(sha256(verifier)).

PKCE обязателен. В discovery объявлены и S256, и plain, но обмен кода без code_verifier сейчас заканчивается ошибкой invalid_grant. Используйте S256 — это и работает, и правильно.

Шаг 2. Обменять код на токены

После подтверждения пользователь вернётся на ваш redirect_uri с параметром code. Обменяйте его — запросом со своего сервера, не из браузера.

POST https://id.ansible.su/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=<AUTHORIZATION_CODE>&
redirect_uri=<YOUR_CALLBACK_URL>&
client_id=<YOUR_CLIENT_ID>&
client_secret=<YOUR_CLIENT_SECRET>&
code_verifier=<PKCE_VERIFIER>

Аутентификация клиента — client_secret_post: секрет идёт в теле запроса. HTTP Basic наш сервер не читает, и заголовок Authorization здесь ничего не даст.

Ответ:

{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "scope": "openid profile"
}

Код одноразовый и живёт 10 минут. refresh_token не выдаётся: срок жизни access_token — час, дальше нужен новый вход.

Шаг 3. Проверить ID-токен

id_token — это подписанный JWT. Прежде чем поверить тому, что внутри, проверьте подпись:

  1. Возьмите ключи с https://id.ansible.su/.well-known/jwks.json — по kid из заголовка токена.
  2. Проверьте подпись алгоритмом RS256.
  3. Проверьте поля: iss равен https://id.ansible.su, aud — вашему client_id, exp не в прошлом, а nonce совпадает с тем, что вы отправляли.

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

Алгоритм подписи

Токены подписываются RS256 — ключом RSA-2048. Других алгоритмов сейчас нет.

АлгоритмСостояние
RS256Единственный. Работает со всеми библиотеками OIDC без настройки.

В JWKS всегда ровно один активный ключ. Выбор алгоритма на стороне приложения не предусмотрен.