Подключение
Инвойсбокс ID работает по OAuth 2.1, поток Authorization Code с обязательным PKCE. Пользователь подтверждает доступ на стороне Инвойсбокс, ваш сервис получает одноразовый код и обменивает его на токен — уже со своего сервера.
Ниже — тот же поток, что в справочнике по стандарту, но по шагам и с кодом.
Названия параметров стандартные, поэтому всё это умеет любая библиотека OAuth: если вы берёте
библиотеку, достаточно указать ей issuer — https://api.invoicebox.ru.
Шаг 1. Подготовьте PKCE
PKCE защищает обмен кода без секрета приложения: сервис создаёт случайную строку code_verifier,
передаёт в Инвойсбокс ID только её хеш, а при обмене показывает исходную строку. Перехваченный код без
code_verifier бесполезен.
const b64url = (buf) =>
btoa(String.fromCharCode(...new Uint8Array(buf)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
const random = (len = 64) => {
const arr = new Uint8Array(len);
crypto.getRandomValues(arr);
return b64url(arr.buffer).slice(0, len);
};
const codeVerifier = random(64);
const state = random(32);
const codeChallenge = b64url(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)));
// verifier и state понадобятся после возврата — держите их в sessionStorage
sessionStorage.setItem('ib_pkce_verifier', codeVerifier);
sessionStorage.setItem('ib_oauth_state', state);
state — случайная строка, которую Инвойсбокс ID вернёт без изменений. Сравнив её после возврата, вы
убеждаетесь, что пришли из своего же запроса, а не по чужой ссылке.
Шаг 2. Отправьте пользователя на экран согласия
const params = new URLSearchParams({
response_type: 'code',
client_id: 'ваш-идентификатор-приложения',
redirect_uri: `${location.origin}/auth/callback/`,
state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
scope: 'merchant-read',
});
location.href = `https://id.invoicebox.ru/oauth/authorize?${params}`;
Адрес лучше не хранить в коде, а брать из метаданных сервера — тогда переезд адресов вас не коснётся.
redirect_uri должен точно совпадать с тем, который вы прислали в
службу поддержки при получении идентификатора приложения:
Инвойсбокс ID возвращает пользователя только на заранее известный адрес. scope можно не передавать —
тогда запрашиваются все права приложения; лучше передавать и просить ровно то, что нужно.
Шаг 3. Примите возврат
Инвойсбокс ID вернёт пользователя на redirect_uri с параметрами code и state. Если человек нажал
«Отклонить», вместо кода придёт error=access_denied. Это тоже ответ, и обработать его нужно так же, как код:
иначе страница будет ждать того, что уже не придёт.
Страница возврата должна сверить state, забрать code_verifier и отдать оба значения на свой
сервер — обмен кода в браузере оставил бы токен в JavaScript, откуда его достанет любой сторонний
скрипт.
const url = new URL(location.href);
const code = url.searchParams.get('code');
const returned = url.searchParams.get('state');
const error = url.searchParams.get('error');
if (error || !code || returned !== sessionStorage.getItem('ib_oauth_state')) {
// отказ пользователя, чужой или повторно открытый адрес — вход не начинаем
location.replace('/');
} else {
await fetch('/api/auth/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, codeVerifier: sessionStorage.getItem('ib_pkce_verifier') }),
});
sessionStorage.removeItem('ib_pkce_verifier');
sessionStorage.removeItem('ib_oauth_state');
}
Шаг 4. Обменяйте код на токен
Обмен делает ваш сервер, тело — application/x-www-form-urlencoded:
curl -X POST https://api.invoicebox.ru/v3/security/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code' \
-d 'client_id=ваш-идентификатор-приложения' \
-d 'code=полученный-код' \
-d 'redirect_uri=https://ваш-сервис.ru/auth/callback/' \
-d 'code_verifier=сохранённый-verifier'
В ответе — access_token, expires_in в секундах и refresh_token. Полный разбор ответа — на
странице Обмен кода на токен, дальше по потоку —
обновление и отзыв доступа.
Секрет приложения нужен только тем, кто хранит его на сервере: приложению в браузере или на устройстве
он не выдаётся, подлинность обмена подтверждает code_verifier.
Шаг 5. Проверьте, кто вошёл
Токен без проверки ничего не значит: вызовите метод профиля и убедитесь, что userId не пустой.
GET /v3/security/api/auth/auth
Authorization: Bearer полученный-токен
Accept: application/json
200 с userId: null означает, что токен не передан или не распознан — это не успешный вход, см.
авторизацию.
Дальше токен нужно положить в серверную сессию и не отдавать в браузер — как это сделать, описано в сессии и безопасности.