# Инвойсбокс API — Инвойсбокс ID > Раздел документации целиком. Полное оглавление: https://docs.invoicebox.ru/llms.txt ## Инвойсбокс ID - [Инвойсбокс ID](https://docs.invoicebox.ru/raw/id/id.md) - [Подключение](https://docs.invoicebox.ru/raw/id/connect.md) (раздел «Инвойсбокс ID») - [Стандартный OAuth 2.1](https://docs.invoicebox.ru/raw/id/oauth.md) (раздел «Инвойсбокс ID») - [Справочник запросов](https://docs.invoicebox.ru/raw/id/reference.md) (раздел «Инвойсбокс ID») - [Сессия и безопасность](https://docs.invoicebox.ru/raw/id/session.md) (раздел «Инвойсбокс ID») - [Экран согласия](https://docs.invoicebox.ru/raw/id/oauth/consent.md) (раздел «Стандартный OAuth 2.1») - [Обмен кода на токен](https://docs.invoicebox.ru/raw/id/oauth/token.md) (раздел «Стандартный OAuth 2.1») - [Обновление токена](https://docs.invoicebox.ru/raw/id/oauth/refresh.md) (раздел «Стандартный OAuth 2.1») - [Отзыв доступа](https://docs.invoicebox.ru/raw/id/oauth/revoke.md) (раздел «Стандартный OAuth 2.1») --- # Инвойсбокс ID Инвойсбокс ID — единая учётная запись Инвойсбокс. Внешний сервис может пускать по ней своих пользователей: человек нажимает «Войти», подтверждает вход на `id.invoicebox.ru` и возвращается к вам уже авторизованным. Пароль, второй фактор и восстановление доступа остаются на стороне Инвойсбокс — вам не нужно ни хранить пароли, ни делать формы регистрации. ## Что даёт готовый вход - **Не нужно строить свой контур входа.** Регистрация, восстановление пароля, второй фактор, блокировка после неудачных попыток, письма с подтверждением — всё это уже работает и поддерживается. - **Пароли не попадают в ваш сервис.** Их нельзя утечь оттуда, где их нет: вы храните только сессию, а требования к хранению секретов остаются на стороне Инвойсбокс. - **Один вход на все продукты.** Пользователь, у которого уже есть учётная запись Инвойсбокс, входит в ваш сервис без новой регистрации — и не заводит ещё один пароль. - **Вызовы идут от имени человека.** Токен привязан к пользователю, поэтому в логах и в правах видно, кто именно выставил счёт или запросил документы, — в отличие от общего сервисного токена магазина. - **Стандарт, а не самодельный протокол.** OAuth 2.1 с PKCE поддерживают готовые библиотеки почти для любого языка: подключение сводится к двум запросам и одному адресу возврата. Библиотеке достаточно указать `issuer` — остальные адреса она прочитает из [метаданных сервера](/docs/id/oauth/#метаданные-сервера-авторизации). ## Когда это нужно - Сервис показывает пользователю его заказы, счета или документы из Инвойсбокс. - Вы делаете кабинет для покупателей или партнёров и не хотите вести свою базу паролей. - Нужно, чтобы вызовы API шли от имени конкретного человека, а не от сервисного токена магазина. Если сервису нужен только приём платежей, вход не требуется: заказы создаются [токеном магазина](/docs/api/auth/), а покупателю достаточно платёжной страницы. ## Что понадобится | Что | Откуда взять | |---|---| | Идентификатор приложения (`client_id`) | **только через службу поддержки** — см. ниже | | Адрес возврата (`redirect_uri`) | ваш адрес, его нужно прислать вместе с заявкой | | Адрес Инвойсбокс ID | `https://id.invoicebox.ru` | | Адрес API для обмена кода | `https://api.invoicebox.ru` | ## Как получить идентификатор приложения Самостоятельной регистрации приложений пока нет. Идентификатор выдаёт [служба поддержки](https://www.invoicebox.ru/ru/contacts) — напишите ей и укажите: 1. **название сервиса** и кратко, зачем нужен вход; 2. **адреса возврата** (`redirect_uri`) — все, которые будете использовать, включая адрес для разработки. Инвойсбокс ID вернёт пользователя только на заранее известный адрес: незарегистрированный адрес приводит к отказу входа; 3. контакт разработчика, с которым можно уточнять детали. Секрет приложения нужен не всем. Приложению, которое работает в браузере или на устройстве, он не выдаётся: спрятать его там невозможно, а подлинность обмена кода подтверждает [PKCE](/docs/id/connect/). Серверному приложению секрет выдаётся по запросу — скажите об этом сразу, если обмен кода у вас идёт с сервера. ## Что в разделе - [Подключение](/docs/id/connect/) — вход по шагам, с кодом: PKCE, экран согласия, обмен кода. - [Стандартный OAuth 2.1](/docs/id/oauth/) — справочник по стандарту: метаданные сервера, параметры экрана согласия, обмен, обновление и отзыв токена, коды ошибок. - [Справочник запросов](/docs/id/reference/) — что нужно после входа: профиль пользователя и выход. - [Сессия и безопасность](/docs/id/session/) — где держать токен, зачем `state`, какие ошибки бывают. --- --- # Подключение Инвойсбокс ID работает по OAuth 2.1, поток Authorization Code с обязательным PKCE. Пользователь подтверждает доступ на стороне Инвойсбокс, ваш сервис получает одноразовый код и обменивает его на токен — уже со своего сервера. Ниже — тот же поток, что в [справочнике по стандарту](/docs/id/oauth/), но по шагам и с кодом. Названия параметров стандартные, поэтому всё это умеет любая библиотека OAuth: если вы берёте библиотеку, достаточно указать ей `issuer` — `https://api.invoicebox.ru`. ```mermaid sequenceDiagram participant U as Пользователь participant S as Ваш сервис participant ID as Инвойсбокс ID participant API as API Инвойсбокс U->>S: нажимает «Войти» S->>S: создаёт verifier и challenge, запоминает state S->>ID: переход на /oauth/authorize с client_id и code_challenge U->>ID: видит запрошенные права и разрешает доступ ID->>S: возврат на redirect_uri с code и state S->>API: POST /v3/security/oauth/token — code и code_verifier API-->>S: access_token и refresh_token S->>API: GET /auth/auth — профиль по токену API-->>S: userId и данные пользователя ``` ## Шаг 1. Подготовьте PKCE PKCE защищает обмен кода без секрета приложения: сервис создаёт случайную строку `code_verifier`, передаёт в Инвойсбокс ID только её хеш, а при обмене показывает исходную строку. Перехваченный код без `code_verifier` бесполезен. ```js 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. Отправьте пользователя на экран согласия ```js 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}`; ``` Адрес лучше не хранить в коде, а брать из [метаданных сервера](/docs/id/oauth/#метаданные-сервера-авторизации) — тогда переезд адресов вас не коснётся. `redirect_uri` должен точно совпадать с тем, который вы прислали в [службу поддержки](https://www.invoicebox.ru/ru/contacts) при получении идентификатора приложения: Инвойсбокс ID возвращает пользователя только на заранее известный адрес. `scope` можно не передавать — тогда запрашиваются все права приложения; лучше передавать и просить ровно то, что нужно. ## Шаг 3. Примите возврат Инвойсбокс ID вернёт пользователя на `redirect_uri` с параметрами `code` и `state`. Если человек нажал «Отклонить», вместо кода придёт `error=access_denied`. Это тоже ответ, и обработать его нужно так же, как код: иначе страница будет ждать того, что уже не придёт. Страница возврата должна сверить `state`, забрать `code_verifier` и отдать оба значения **на свой сервер** — обмен кода в браузере оставил бы токен в JavaScript, откуда его достанет любой сторонний скрипт. ```js 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`: ```bash 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`. Полный разбор ответа — на странице [Обмен кода на токен](/docs/id/oauth/token/), дальше по потоку — [обновление](/docs/id/oauth/refresh/) и [отзыв доступа](/docs/id/oauth/revoke/). Секрет приложения нужен только тем, кто хранит его на сервере: приложению в браузере или на устройстве он не выдаётся, подлинность обмена подтверждает `code_verifier`. ## Шаг 5. Проверьте, кто вошёл Токен без проверки ничего не значит: вызовите метод профиля и убедитесь, что `userId` не пустой. ```http GET /v3/security/api/auth/auth Authorization: Bearer полученный-токен Accept: application/json ``` `200` с `userId: null` означает, что токен не передан или не распознан — это не успешный вход, см. [авторизацию](/docs/api/auth/). Дальше токен нужно положить в серверную сессию и не отдавать в браузер — как это сделать, описано в [сессии и безопасности](/docs/id/session/). ## Читайте также - [Стандартный OAuth 2.1](/docs/id/oauth/) - [Справочник запросов](/docs/id/reference/) - [Сессия и безопасность](/docs/id/session/) --- --- # Стандартный OAuth 2.1 Инвойсбокс ID подключается по стандарту. Параметры и ответы называются так, как их ждут готовые библиотеки OAuth, поэтому обмен не приходится писать руками. Библиотеке достаточно указать `issuer` — `https://api.invoicebox.ru`, остальные адреса она прочитает из метаданных. ## Что нужно получить заранее Идентификатор приложения (`client_id`), список адресов возврата и набор прав запрашиваются у службы поддержки. Права — это именованные наборы, те же, что видны в личном кабинете: приложение не может попросить больше, чем ему разрешили при регистрации, а пользователь на экране согласия видит их человеческими названиями, а не кодами. ### Какие права бывают Перечень полный — `scope` принимает только эти коды, через пробел. | Код | Что разрешает | |---|---| | `merchant-read` | просмотр заказов и платежей | | `merchant-order` | создание заказов | | `merchant-refund` | возвраты | | `merchant-notification-read` | просмотр журнала уведомлений | | `merchant-notification-resend` | переотправка уведомлений из журнала | | `merchant-document-read` | просмотр отчётных документов | Просить больше, чем нужно, не надо. Пользователь видит запрошенное на экране согласия, и лишняя строка — это причина отказать. Незнакомый код или право, которого у приложения нет, отклоняются с `invalid_scope`. Параметр `scope` можно не передавать — тогда запрашиваются **все** права, разрешённые приложению при регистрации. Это не «минимум», а «максимум»: удобно начинать, но для боевого запуска перечисляйте нужное явно. Список прав приложения со временем расширяется, и «все» завтра означает больше, чем сегодня. Со своей стороны мы просим у службы поддержки узкий список прав для новых приложений — ровно под задачу, а не «как у менеджера магазина». Если вам выдали больше, чем нужно, скажите: сузить список проще, чем потом объяснять пользователю, почему на экране согласия просят доступ к возвратам. Что пользователь **действительно** разрешил, говорит не ваш запрос, а ответ: поле `scope` при обмене кода и поле `scopes` в [профиле](/docs/id/reference/#профиль-пользователя). Проверять права нужно по ним. Секрет приложения выдаётся только тем, кто хранит его на сервере. Приложению, которое работает в браузере или на устройстве, секрет не нужен и не выдаётся: его невозможно там спрятать. ## Метаданные сервера авторизации Адреса не нужно хранить в коде — сервер сообщает их сам (RFC 8414). - метод: `GET` - ресурс: `/.well-known/oauth-authorization-server` ```json { "issuer": "https://api.invoicebox.ru", "authorization_endpoint": "https://id.invoicebox.ru/oauth/authorize", "token_endpoint": "https://api.invoicebox.ru/v3/security/oauth/token", "revocation_endpoint": "https://api.invoicebox.ru/v3/security/oauth/revoke", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["client_secret_post", "none"] } ``` Библиотеки OAuth читают этот документ сами, и обычно достаточно указать им `issuer`. Ответ `404` означает, что стандартные адреса в этой установке ещё не включены. Обратите внимание, где что лежит. Сам документ — в корне хоста: его адрес выводится из `issuer` по правилам стандарта, и клиент собирает его сам. А адреса обмена и отзыва берутся **из документа** и лежат вместе с остальным API, под `/v3/security/oauth/`. Не собирайте их руками из `issuer` — читайте из метаданных, тогда переезд адресов вас не коснётся. Рядом лежит второй документ — `/.well-known/oauth-protected-resource`: он описывает уже сам API как ресурс и называет сервер авторизации, у которого для него просят токен. Именно на него указывает подсказка в отказе `401`. ## Как проходит вход Четыре запроса, каждый на своей странице — по порядку, в котором они и делаются: 1. [Экран согласия](/docs/id/oauth/consent/) — пользователь видит, какое приложение просит доступ и какие права, и разрешает или отклоняет. 2. [Обмен кода на токен](/docs/id/oauth/token/) — ваш сервер меняет одноразовый код на пару токенов. 3. [Обновление токена](/docs/id/oauth/refresh/) — когда срок access-токена истёк. 4. [Отзыв доступа](/docs/id/oauth/revoke/) — когда доступ больше не нужен. Ниже — то, что относится к потоку целиком: ограничение токена одним сервисом, обязательность PKCE и коды ошибок. ## Ограничение токена одним сервисом Параметр `resource` (RFC 8707) говорит, для какого нашего сервиса нужен токен. Указали — токен принимается только там; в другом сервисе он не сработает, даже если прав хватает. Это защита от посредника. Приложение, которому доверили доступ к одному сервису, не должно уметь тем же токеном сходить в другой — а без указанного ресурса токен принимается всюду, где хватает прав. Значение — адрес сервиса, целиком и без якоря, например `https://mcp.invoicebox.ru/mcp`. Незнакомый адрес отклоняется с `invalid_target`: список известных ресурсов задаём мы, иначе токен получил бы аудиторию, которую никто не проверяет. Указанный на первом шаге ресурс должен повториться при обмене кода — иначе приложение получило бы доступ не туда, куда согласился пользователь. ## PKCE обязателен `code_challenge` и `code_verifier` нужны всем приложениям, включая серверные с секретом. Причина не в формальности: код возвращается через браузер пользователя, а секрет приложения в этом обмене не участвует — перехваченный код без `code_verifier` бесполезен, с секретом или без. Поддерживается только метод `S256`. Приложения, зарегистрированные до этого требования, продолжают работать по-прежнему, но при обновлении клиента стоит добавить PKCE: включение по одному приложению делается по вашему запросу. ## Ошибки Отказы приходят по стандарту: код в поле `error`, пояснение для разработчика — в `error_description`. ```json { "error": "invalid_grant", "error_description": "code: authorization code has expired" } ``` | Код | Что случилось | Что делать | |---|---|---| | `invalid_request` | не хватает параметра или он пустой | исправить запрос | | `invalid_client` | `client_id` неизвестен или приложение отключено | проверить идентификатор; отвечает `401` | | `invalid_grant` | код или токен обновления не подошёл: истёк, уже использован, выпущен для другого адреса возврата | пройти авторизацию заново | | `unsupported_grant_type` | такой способ получения токена не поддерживается | использовать `authorization_code` или `refresh_token` | | `invalid_scope` | запрошены права, которых у приложения нет | согласовать права с поддержкой | | `invalid_target` | незнакомый или несовпадающий `resource` | проверить адрес сервиса | ## Читайте также - [Подключение](/docs/id/connect/) — тот же поток по шагам, с кодом - [Справочник запросов](/docs/id/reference/) — что нужно после входа: профиль и выход - [Сессия и безопасность](/docs/id/session/) — где держать токен, зачем `state` и PKCE - [Авторизация запросов к API](/docs/api/auth/) - [Безопасность работы с API](/docs/security/) — хранение токена и что делать при утечке --- --- # Справочник запросов Здесь запросы, которые нужны сервису после входа: узнать, кто вошёл, и завершить сессию. Они одинаковы независимо от того, как получен токен. ## Весь поток на одной карте Если вы попали сюда первым, вот порядок целиком — по ссылкам полные параметры и примеры: 1. [Экран согласия](/docs/id/oauth/consent/) — `GET https://id.invoicebox.ru/oauth/authorize`, пользователь разрешает доступ, вы получаете `code`. 2. [Обмен кода на токен](/docs/id/oauth/token/) — `POST /v3/security/oauth/token`, `grant_type=authorization_code`; в ответе `access_token`, `expires_in`, `refresh_token`. 3. **Профиль пользователя** — `GET /v3/security/api/auth/auth` (ниже на этой странице): кто вошёл и какие права выданы. 4. [Обновление токена](/docs/id/oauth/refresh/) — когда `expires_in` истёк. 5. [Отзыв доступа](/docs/id/oauth/revoke/) и **завершение сессии** (ниже) — когда доступ больше не нужен. Адреса не храните в коде: их сообщают [метаданные сервера](/docs/id/oauth/#метаданные-сервера-авторизации). Параметры и ответы называются как в стандарте, поэтому подходит любая готовая библиотека OAuth. > [!IMPORTANT] > Это OAuth 2.1, а не OpenID Connect. `id_token` мы не выдаём, документа > `/.well-known/openid-configuration` у нас нет (ни на `api.invoicebox.ru`, ни на > `id.invoicebox.ru`), метаданные публикуются по RFC 8414. > > Токен считайте непрозрачным. Он может выглядеть как JWT, но JWKS мы не публикуем и формат не > обещаем: разбирать и проверять подпись локально нельзя — сегодня получится, а после смены формата > перестанет. Кто вошёл и какие права — узнавайте запросом профиля ниже. ## Профиль пользователя - метод: `GET` - ресурс: `/v3/security/api/auth/auth` - заголовок: `Authorization: Bearer <токен>` | Параметр строки запроса | Обязательный | Что делает | |---|---|---| | `withPermissions` | нет | `true` — в ответе появится массив `permissions` с правами пользователя внутри организаций. Без параметра массива нет: он стоит отдельного обращения к правам, и платить за него каждой проверкой сессии незачем | ```json { "data": { "userId": "01771533-8e75-3234-8e3d-9213ae2d7c52", "profile": { "firstName": "Пётр", "lastName": "Смирнов", "email": "buh@example.invbox.ru" } } } ``` | Поле ответа | Что означает | |---|---| | `userId` | идентификатор пользователя; `null` — токен не принят | | `profile` | имя, фамилия, электронная почта | | `userIdentifier` | чем пользователь идентифицируется при входе: сама строка входа (телефон или почта) и признак подтверждения | | `applicationId` | приложение, которому выдан токен — сверяйте со своим | | `scopes` | права, которые пользователь действительно разрешил: именно этот список, а не тот, что вы просили в `scope` | | `permissions` | только с `withPermissions=true`: права внутри организаций | > [!IMPORTANT] > Код `200` не означает успешную авторизацию. Ответ с `userId: null` приходит и тогда, когда токен не > передан или не распознан. Признак рабочего токена — непустой `userId`. Этим же запросом удобно проверять, жива ли сессия, перед показом страниц пользователя. ## Завершение сессии - метод: `DELETE` - ресурс: `/v3/security/api/auth/logout` - заголовок: `Authorization: Bearer <токен>` Запрос завершает сессию на стороне Инвойсбокс. Свои куки сервис удаляет сам — если оставить их, человек останется «войденным» в интерфейсе с уже недействительным токеном. Этот запрос завершает сессию, но не отзывает выданный токен. Чтобы приложение перестало иметь доступ совсем, нужен [отзыв доступа](/docs/id/oauth/revoke/) — или отзыв со стороны пользователя в личном кабинете. ## Читайте также - [Стандартный OAuth 2.1](/docs/id/oauth/) - [Сессия и безопасность](/docs/id/session/) - [Авторизация запросов к API](/docs/api/auth/) --- --- # Сессия и безопасность ## Где держать токен Токен доступа — это право действовать от имени человека. Он не должен попадать в JavaScript: любой сторонний скрипт на странице, любое расширение браузера и любая XSS-уязвимость немедленно превращаются в кражу доступа. Рабочая схема: 1. код обменивает **сервер**, а не браузер; 2. полученный токен сервер кладёт в куку с флагами `HttpOnly`, `Secure` и `SameSite=Lax`; 3. браузер к API напрямую не ходит — запросы идут через ваш сервер, который подставляет токен. ``` Set-Cookie: sid=<токен>; Path=/; HttpOnly; SameSite=Lax; Secure; Max-Age=43200 ``` Срок жизни сессии выбирает сервис. Удобный вариант — 12 часов со скользящим продлением: пока человек работает, сессия продлевается, а забытая на чужом компьютере закрывается сама. Если интерфейсу нужно показать, что сессия скоро истечёт, кладите рядом **вторую** куку без `HttpOnly` — с одной лишь отметкой времени. Токен при этом остаётся недоступным для скриптов. Правило «токен не отдают в браузер» общее для всех токенов Инвойсбокс — где им место и где нет, разобрано в [безопасности работы с API](/docs/security/#где-он-должен-и-не-должен-быть). Оттуда же берите порядок действий, [если токен всё-таки утёк](/docs/security/#если-токен-утёк): для токена пользователя он тот же, только вместо смены ключа в кабинете — завершение сессии и отзыв доступа приложению. Срок жизни и ротация из общих правил относятся к токену интеграции: у него нет владельца-человека, и он живёт месяцами. Сессия пользователя короткая по замыслу, продлевать её ротацией не нужно — истёкшую сессию заменяет новый вход. ## Зачем `state` и PKCE | Что | От чего защищает | |---|---| | `state` | подмена возврата: без сверки злоумышленник может привести пользователя по своей ссылке с чужим кодом | | `code_verifier` (PKCE) | перехват кода: код без исходной строки не обменивается на токен | | зарегистрированный `redirect_uri` | угон кода на чужой адрес | Все три проверки обязательны. `state` и `code_verifier` держите в `sessionStorage` — они нужны ровно до возврата и не должны жить дольше вкладки. После обмена удаляйте оба. ## Требования к адресу возврата - Адрес присылается в [службу поддержки](https://www.invoicebox.ru/ru/contacts) вместе с заявкой на идентификатор приложения. Незарегистрированный адрес не сработает. - Боевой адрес — только `https`. Для разработки запросите отдельный адрес и отдельный идентификатор приложения, чтобы боевой не пришлось отдавать на локальную машину. - Адрес в запросе авторизации и в обмене кода должен совпадать посимвольно, включая завершающий слэш. - Не ведите возврат на страницу, которая сама по себе перенаправляет пользователя дальше по адресу из параметра: так открывается дыра в чужой сайт. Куда вернуть человека после входа, храните у себя — и принимайте только внутренние пути. ## Ошибки при входе | Что произошло | Что видно | Что делать | |---|---|---| | `redirect_uri` не зарегистрирован или не совпал | возврат не происходит, Инвойсбокс ID показывает ошибку | сверить адрес с тем, что зарегистрирован, вплоть до слэша | | `state` не совпал | ваша страница возврата не должна продолжать вход | начать вход заново; повторный переход по старой ссылке — нормальная причина | | код уже использован | обмен отвечает ошибкой | код одноразовый: повторный обмен не делают, нужен новый вход | | токен не принят | `200` с `userId: null` | считать, что входа нет: очистить куки и предложить войти снова | | срок токена истёк | обмен или вызов API отвечает отказом | это не ошибка входа: [обновите токен](/docs/id/oauth/refresh/) по `refresh_token`, а вход начинайте заново только если и он не подошёл | | Инвойсбокс ID недоступен | обмен не завершился | показать человеку, что вход временно недоступен, и не создавать пустую сессию | ## Читайте также - [Подключение](/docs/id/connect/) - [Справочник запросов](/docs/id/reference/) - [Безопасность работы с API](/docs/security/) — общие правила для токена интеграции --- --- # Экран согласия Пользователя отправляют на `authorization_endpoint` из метаданных. | Параметр | Обязательный | Что означает | |---|---|---| | `response_type` | да | `code` | | `client_id` | да | идентификатор приложения | | `redirect_uri` | да | адрес возврата; должен совпадать с зарегистрированным | | `state` | да | случайная строка; вернётся без изменений, по ней сверяется подлинность возврата | | `code_challenge` | да | SHA-256 от `code_verifier` в base64url | | `code_challenge_method` | да | `S256` | | `scope` | нет | запрашиваемые права через пробел; без него запрашиваются все права приложения | | `resource` | нет | адрес сервиса, для которого нужен токен (см. ниже) | ``` https://id.invoicebox.ru/oauth/authorize?response_type=code&client_id=my-service&redirect_uri=https%3A%2F%2Fmy-service.ru%2Fauth%2Fcallback%2F&state=8f2c…&code_challenge=k7Yb…&code_challenge_method=S256&scope=merchant-read ``` Пользователь входит (или уже вошёл) и видит, какое приложение просит доступ и какие права. Дальше одно из двух: - нажал «Разрешить» — возврат на `redirect_uri` с параметрами `code` и `state`; - нажал «Отклонить» — возврат на `redirect_uri` с `error=access_denied` и тем же `state`. Отказ приходит именно ответом, а не молчанием: приложение должно узнать, что доступ не дали, а не ждать код, который никогда не придёт. Если параметры запроса неверны — незнакомый `client_id`, чужой адрес возврата, права, которых у приложения нет, — пользователь остаётся на нашей странице с объяснением. По непроверенному адресу возврата мы никого не отправляем: именно так работает открытый редирект. ## Читайте также - [Обмен кода на токен](/docs/id/oauth/token/) - [Стандартный OAuth 2.1](/docs/id/oauth/) - [Подключение по шагам](/docs/id/connect/) --- --- # Обмен кода на токен - метод: `POST` - ресурс: `token_endpoint` из метаданных - тело: `application/x-www-form-urlencoded` - вызывается **с сервера**: токен не должен попадать в браузер | Параметр | Обязательный | Что означает | |---|---|---| | `grant_type` | да | `authorization_code` | | `client_id` | да | идентификатор приложения | | `code` | да | код из параметров возврата | | `redirect_uri` | да | тот же адрес, что в запросе авторизации | | `code_verifier` | да | исходная строка, от которой считался `code_challenge` | | `client_secret` | нет | только если для приложения выпущен секрет | | `resource` | нет | тот же адрес, что в запросе авторизации | ```bash 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=my-service' \ -d 'code=e3b0c44298fc1c14' \ -d 'redirect_uri=https://my-service.ru/auth/callback/' \ -d 'code_verifier=s9Zx1kQd…' ``` Ответ — без общей оболочки `data`, поля называются по стандарту: ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "def50200a1b2…", "scope": "merchant-read" } ``` `expires_in` — срок в секундах от момента ответа. Код обмена одноразовый: повторный запрос с тем же `code` вернёт `invalid_grant`. С полученным токеном запросы к API подписываются заголовком `Authorization: Bearer <токен>` — как и при [обычной авторизации](/docs/api/auth/). ## Читайте также - [Обновление токена](/docs/id/oauth/refresh/) - [Экран согласия](/docs/id/oauth/consent/) - [Сессия и безопасность](/docs/id/session/) --- --- # Обновление токена - те же адрес и формат тела - `grant_type=refresh_token`, `client_id`, `refresh_token` ```bash curl -X POST https://api.invoicebox.ru/v3/security/oauth/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=refresh_token' \ -d 'client_id=my-service' \ -d 'refresh_token=def50200a1b2…' ``` Ответ — той же формы, что при обмене кода: ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "def50200c3d4…", "scope": "merchant-read" } ``` | Поле | Тип | Что означает | |---|---|---| | `access_token` | string | новый токен доступа; прежний перестаёт действовать | | `token_type` | string | всегда `Bearer` | | `expires_in` | integer | срок в секундах от момента ответа | | `refresh_token` | string | **новый** токен обновления: прежний недействителен, храните только последний | | `scope` | string | права через пробел; шире прежних они не станут | > [!WARNING] > Повторное использование уже обменянного `refresh_token` отзывает всю цепочку: и тот токен, что > предъявили, и тот, что был выдан вместо него. Пользователю придётся заново дать доступ. > > Так сделано намеренно. Если старый токен обновления пришёл во второй раз, значит он есть у кого-то > ещё, — и правильный ответ здесь не «отказать в одном запросе», а прекратить доступ по всей цепочке. > Практический вывод для вас: не обновляйте токен из двух процессов одновременно и не повторяйте > запрос обновления при таймауте, не проверив, не пришёл ли ответ. ## Читайте также - [Отзыв доступа](/docs/id/oauth/revoke/) - [Обмен кода на токен](/docs/id/oauth/token/) - [Что делать при утечке токена](/docs/security/#если-токен-утёк) --- --- # Отзыв доступа - метод: `POST` - ресурс: `revocation_endpoint` из метаданных - тело: `application/x-www-form-urlencoded`, параметры `client_id`, `token`, необязательный `token_type_hint` (`refresh_token` или `access_token`) ```bash curl -X POST https://api.invoicebox.ru/v3/security/oauth/revoke \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'client_id=my-service' \ -d 'token=def50200a1b2…' \ -d 'token_type_hint=refresh_token' ``` Ответ — пустой `200`, в том числе на неизвестный или уже отозванный токен. Это требование стандарта: иначе по ответу можно было бы перебором выяснять, какие токены ещё действуют. Отзыв токена обновления прекращает доступ и по токенам, выданным его обновлением. Пользователь со своей стороны отзывает доступ в личном кабинете, и делать для этого ничего не нужно. ## Читайте также - [Обновление токена](/docs/id/oauth/refresh/) - [Сессия и безопасность](/docs/id/session/) - [Безопасность работы с API](/docs/security/) ---