К содержимому

Подключение

Инвойсбокс ID работает по OAuth 2.0, поток Authorization Code с расширением PKCE. Пользователь подтверждает вход на стороне Инвойсбокса, ваш сервис получает одноразовый код и обменивает его на токен — уже со своего сервера.

sequenceDiagram participant U as Пользователь participant S as Ваш сервис participant ID as Инвойсбокс ID participant API as API Инвойсбокс U->>S: нажимает «Войти» S->>S: создаёт verifier и challenge, запоминает state S->>ID: переход на id.invoicebox.ru с applicationId и challenge U->>ID: подтверждает вход ID->>S: возврат на redirectUri с code и state S->>API: POST /auth/token — code и verifier API-->>S: accessToken S->>API: GET /auth/auth — профиль по токену API-->>S: userId и данные пользователя

Шаг 1. Подготовьте PKCE

PKCE защищает обмен кода без секрета приложения: сервис создаёт случайную строку codeVerifier, передаёт в Инвойсбокс ID только её хеш, а при обмене показывает исходную строку. Перехваченный код без codeVerifier бесполезен.

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. Отправьте пользователя на Инвойсбокс ID

const params = new URLSearchParams({
    applicationId: 'ваш-идентификатор-приложения',
    redirectUri: `${location.origin}/auth/callback/`,
    state,
    codeChallenge,
    codeChallengeMethod: 'S256',
});
location.href = `https://id.invoicebox.ru/?${params}`;

redirectUri должен точно совпадать с тем, который вы прислали в службу поддержки при получении идентификатора приложения: Инвойсбокс ID возвращает пользователя только на заранее известный адрес.

Шаг 3. Примите возврат

Инвойсбокс ID вернёт пользователя на redirectUri с параметрами code и state. Страница возврата должна сверить state, забрать codeVerifier и отдать оба значения на свой сервер — обмен кода в браузере оставил бы токен в JavaScript, откуда его достанет любой сторонний скрипт.

const url = new URL(location.href);
const code = url.searchParams.get('code');
const returned = url.searchParams.get('state');

if (!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. Обменяйте код на токен

Обмен делает ваш сервер:

POST /v3/security/api/auth/token
Content-Type: application/json
Accept: application/json

{
  "applicationId": "ваш-идентификатор-приложения",
  "redirectUri": "https://ваш-сервис.ru/auth/callback/",
  "code": "полученный-код",
  "codeVerifier": "сохранённый-verifier"
}

В ответе приходит accessToken в свойстве data. Полный разбор ответа и остальных запросов — в справочнике.

Секрет приложения в запросе не нужен: приложения Инвойсбокс ID публичные, подлинность обмена подтверждает codeVerifier.

Шаг 5. Проверьте, кто вошёл

Токен без проверки ничего не значит: вызовите метод профиля и убедитесь, что userId не пустой.

GET /v3/security/api/auth/auth
Authorization: Bearer полученный-токен
Accept: application/json

200 с userId: null означает, что токен не передан или не распознан — это не успешный вход, см. авторизацию.

Дальше токен нужно положить в серверную сессию и не отдавать в браузер — как это сделать, описано в сессии и безопасности.

Читайте также

Страница помогла?