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

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

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

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

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

![Приложение «Конспект»: блок «Подключите RouterAI» с кнопкой «Войти через RouterAI»](https://b51bc417-3ee0-451a-a83b-8a681d027a96.selstorage.ru/public/0de128ef564c1f5c7635c39a1332df51.png)

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

![Экран согласия RouterAI: приложение «Конспект» запрашивает API-ключ, поля аккаунта, названия ключа и лимита расходов](https://b51bc417-3ee0-451a-a83b-8a681d027a96.selstorage.ru/public/aadfeb9d02ef150f17307dddb72220a6.png)

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

![Приложение «Конспект» после входа: отметка «RouterAI подключён» и готовый пересказ текста](https://b51bc417-3ee0-451a-a83b-8a681d027a96.selstorage.ru/public/beb5656e2195299f4dbf0d970753c191.png)

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

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

- **Лимит расходов.** Приложение может предложить своё значение, а решает пользователь. Если приложение лимит не указало, в форме стоит 100 ₽ в день. Когда лимит исчерпан, запросы отклоняются.
- **Аккаунт.** Расходы идут с баланса того аккаунта, который выбран на экране согласия.
- **Отзыв в любой момент.** Ключ появляется в [настройках ключей](https://routerai.ru/settings/keys) с пометкой «OAuth» и адресом приложения. Там можно изменить лимит, выключить ключ или удалить его.

![Карточка ключа в кабинете: название «Конспект», пометка OAuth с адресом приложения, расходы 0,03 ₽ из 50 ₽ в месяц](https://b51bc417-3ee0-451a-a83b-8a681d027a96.selstorage.ru/public/ece489bdffd92d74335e3d5115fab224.png)

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

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

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

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

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

```javascript
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`:

```javascript
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](https://routerai.ru/docs/guides/overview/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](https://b51bc417-3ee0-451a-a83b-8a681d027a96.selstorage.ru/public/96a4d005a2d37f383307e8ab91711449.png)

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

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

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

## Чего пока нет

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

## С чего начать

Если вы делаете приложение, которое работает с моделями, добавьте в него кнопку «Войти через RouterAI». Регистрация и согласование не нужны: возьмите код из примера выше и подставьте свой `callback_url`. Подробности — в [руководстве по OAuth PKCE](https://routerai.ru/docs/guides/overview/oauth-pkce).

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