Загрузка...

Войти через RouterAI: приложение получает API-ключ в один клик по OAuth PKCE

Чтобы подключить стороннее приложение к RouterAI, раньше нужно было зайти в кабинет, создать API-ключ, скопировать его и вставить в настройки приложения. Теперь хватает одной кнопки «Войти через RouterAI». Пользователь подтверждает выдачу ключа на нашей странице, и приложение получает его само.

Работает это по стандарту OAuth 2.0 с PKCE. Регистрировать приложение у нас не нужно, а адреса и поля совпадают с OpenRouter. Ниже показываем, как это выглядит для пользователя и как добавить такую кнопку в свой продукт.

Как это выглядит для пользователя

Для статьи мы написали маленькое веб-приложение «Конспект»: оно пересказывает длинный текст в трёх пунктах. Своего ключа у приложения нет, оно работает на балансе пользователя.

Приложение «Конспект»: блок «Подключите RouterAI» с кнопкой «Войти через RouterAI»

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

Экран согласия RouterAI: приложение «Конспект» запрашивает API-ключ, поля аккаунта, названия ключа и лимита расходов

После «Выдать ключ» пользователь возвращается в приложение, и оно уже подключено. Ключ он не видел, не копировал и никуда не вставлял.

Приложение «Конспект» после входа: отметка «RouterAI подключён» и готовый пересказ текста

Что остаётся под контролем пользователя

Приложение получает обычный API-ключ, но на условиях пользователя:

  • Лимит расходов. Приложение может предложить своё значение, а решает пользователь. Если приложение лимит не указало, в форме стоит 100 ₽ в день. Когда лимит исчерпан, запросы отклоняются.
  • Аккаунт. Расходы идут с баланса того аккаунта, который выбран на экране согласия.
  • Отзыв в любой момент. Ключ появляется в настройках ключей с пометкой «OAuth» и адресом приложения. Там можно изменить лимит, выключить ключ или удалить его.

Карточка ключа в кабинете: название «Конспект», пометка OAuth с адресом приложения, расходы 0,03 ₽ из 50 ₽ в месяц

Пересказ из нашего примера стоил 3 копейки, и они сразу видны в расходах ключа.

Если отклонить запрос, приложение ничего не получит: пользователь вернётся в него с отметкой об отказе.

Как добавить кнопку в своё приложение

Весь обмен состоит из двух шагов: отправить пользователя на /auth и обменять полученный код на ключ. Ниже код из нашего «Конспекта», он работает в браузере без сервера и без библиотек.

Шаг 1. Отправляем пользователя в RouterAI. Приложение придумывает случайную строку code_verifier и оставляет её у себя. В адрес уходит только её хеш, code_challenge.

const ROUTERAI = 'https://routerai.ru';
const CALLBACK = location.origin + location.pathname;

const b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes)))
  .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const random = () => b64url(crypto.getRandomValues(new Uint8Array(32)));

async function login() {
  const verifier = random();
  const state = random();
  const challenge = b64url(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));
  sessionStorage.pkce = JSON.stringify({ verifier, state });

  location.assign(`${ROUTERAI}/auth?` + new URLSearchParams({
    callback_url: CALLBACK,
    code_challenge: challenge,
    code_challenge_method: 'S256',
    state,
    app_name: 'Конспект',
    key_label: 'Конспект',
    limit: 50,
    limit_period: 'month',
  }));
}

app_name показывается на экране согласия. key_label, limit и limit_period подставляются в форму как значения по умолчанию, пользователь может их изменить.

Шаг 2. Меняем код на ключ. RouterAI возвращает пользователя на callback_url с параметрами code и state. Приложение сверяет state и отправляет код вместе с code_verifier:

async function handleCallback() {
  const query = new URLSearchParams(location.search);
  if (!query.has('code') && !query.has('error')) return;
  history.replaceState(null, '', location.pathname); // код не должен остаться в адресной строке

  const { verifier, state } = JSON.parse(sessionStorage.pkce || '{}');
  sessionStorage.removeItem('pkce');
  if (query.get('state') !== state) return fail('Ответ пришёл не на наш запрос.');
  if (query.get('error')) return fail('Вы отклонили выдачу ключа.');

  const response = await fetch(`${ROUTERAI}/api/v1/auth/keys`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code: query.get('code'), code_verifier: verifier, callback_url: CALLBACK }),
  });
  if (!response.ok) return fail('Не удалось получить ключ.');
  const { key } = await response.json(); // обычный ключ sk-…
}

Дальше ключ работает как любой другой: адрес https://routerai.ru/api/v1, заголовок Authorization: Bearer sk-…, те же модели и те же запросы.

Что важно учесть:

  • callback_url должен быть на https. Обычный http разрешён только для localhost и 127.0.0.1, так удобно разрабатывать. Мобильное или десктопное приложение может указать собственную схему, например myapp://callback.
  • При обмене callback_url передаётся тот же, что и в /auth, символ в символ.
  • Код действует 5 минут и срабатывает один раз.
  • Храните ключ как пароль. В веб-приложении с сервером надёжнее делать обмен на сервере и не отдавать ключ в браузер.

Примеры на Python, Node.js и Bash, все параметры и коды ошибок собраны в руководстве по OAuth PKCE.

Если у вас уже есть «Login with OpenRouter»

Пути и названия полей мы сделали такими же, как у OpenRouter: /auth для согласия и /api/v1/auth/keys для обмена. Приложению, которое уже умеет входить через OpenRouter, достаточно заменить хост на https://routerai.ru.

При переносе проверьте три вещи. Поддерживается только метод S256, без plain. Лимит задаётся в рублях. В ответе на обмен приходит только key, без идентификатора пользователя.

Для консольных программ: вход без callback

У CLI-утилиты или скрипта нет адреса, на который можно вернуть пользователя. Для них есть режим без callback_url: программа открывает в браузере ту же страницу /auth, а после подтверждения RouterAI показывает код на экране.

Страница «Код для приложения»: одноразовый код авторизации для программы konspekt-cli

Пользователь вставляет код в программу, и она обменивает его на ключ тем же запросом, только без callback_url.

Что сделано для безопасности

  • Перехваченный код бесполезен. Чтобы обменять его на ключ, нужен code_verifier, а он не покидал приложение. В этом и состоит смысл PKCE.
  • Повторное использование кода выключает ключ. Если уже использованный код предъявить ещё раз вместе с верным code_verifier, значит, утекла вся пара. Выданный по ней ключ перестаёт работать.
  • Ошибки показываем у себя. Если в запросе приложения что-то не так, пользователь видит сообщение на странице RouterAI. На чужой адрес он попадает только после своего решения, выдать ключ или отклонить.
  • Повторный вход не плодит ключи. Когда пользователь снова подключает то же приложение к тому же аккаунту, ключ перевыпускается: старый секрет перестаёт работать, статистика сохраняется. Веб-приложение узнаётся по адресу, локальное и консольное — по app_name.
  • Название приложения — это только подпись. Его присылает само приложение, проверить его мы не можем. Поэтому на экране согласия отдельно показан адрес возврата: ключ получит именно он.

Чего пока нет

  • У выданного ключа нет срока действия. Он работает, пока пользователь его не выключит или не удалит.
  • Приложение получает только ключ. Имя и почту пользователя RouterAI не передаёт.

С чего начать

Если вы делаете приложение, которое работает с моделями, добавьте в него кнопку «Войти через RouterAI». Регистрация и согласование не нужны: возьмите код из примера выше и подставьте свой callback_url. Подробности — в руководстве по OAuth PKCE.

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