Стандартный OAuth 2.1
К Инвойсбокс ID можно подключиться двумя способами, и они не заменяют друг друга.
Поток из справочника — наш исторический: параметры в camelCase, ответы в
общей оболочке data. Он работает и останется работать; если у вас уже написан обмен по нему,
переделывать его не нужно.
Способ на этой странице — тот же поток, но в форме стандарта: параметры и ответы называются так, как их ждут готовые библиотеки OAuth. Выбирайте его, если подключаете внешнее приложение через библиотеку, а не пишете обмен руками, — тогда вам не придётся ничего переучивать.
Важно
Стандартные адреса разворачиваются: пока их не объявили метаданные сервера (см. ниже), пользуйтесь прежним потоком. Проверить готовность можно одним запросом, и это надёжнее любой даты в документации.
Что нужно получить заранее
Идентификатор приложения (client_id), список адресов возврата и набор прав запрашиваются у службы
поддержки. Права — это именованные наборы, те же, что видны в личном кабинете: приложение не может
попросить больше, чем ему разрешили при регистрации, а пользователь на экране согласия видит их
человеческими названиями, а не кодами.
Секрет приложения выдаётся только тем, кто хранит его на сервере. Приложению, которое работает в браузере или на устройстве, секрет не нужен и не выдаётся: его невозможно там спрятать.
Метаданные сервера авторизации
Адреса не нужно хранить в коде — сервер сообщает их сам (RFC 8414).
- метод:
GET - ресурс:
/.well-known/oauth-authorization-server
{
"issuer": "https://api.invoicebox.ru",
"authorization_endpoint": "https://id.invoicebox.ru/oauth/authorize",
"token_endpoint": "https://api.invoicebox.ru/oauth/token",
"revocation_endpoint": "https://api.invoicebox.ru/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
означает, что стандартные адреса в этой установке ещё не включены.
Шаг 1. Экран согласия
Пользователя отправляют на 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, чужой адрес возврата, права, которых у
приложения нет, — пользователь остаётся на нашей странице с объяснением. По непроверенному адресу
возврата мы никого не отправляем: именно так работает открытый редирект.
Шаг 2. Обмен кода на токен
- метод:
POST - ресурс:
token_endpointиз метаданных - тело:
application/x-www-form-urlencoded - вызывается с сервера: токен не должен попадать в браузер
| Параметр | Что означает |
|---|---|
grant_type * | authorization_code |
client_id * | идентификатор приложения |
code * | код из параметров возврата |
redirect_uri * | тот же адрес, что в запросе авторизации |
code_verifier * | исходная строка, от которой считался code_challenge |
client_secret | только если для приложения выпущен секрет |
resource | тот же адрес, что в запросе авторизации |
curl -X POST https://api.invoicebox.ru/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, поля называются по стандарту:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200a1b2…",
"scope": "merchant-read"
}
expires_in — срок в секундах от момента ответа. Код обмена одноразовый: повторный запрос с тем же
code вернёт invalid_grant.
С полученным токеном запросы к API подписываются заголовком Authorization: Bearer <токен> — как и
при обычной авторизации.
Обновление токена
- те же адрес и формат тела
grant_type=refresh_token,client_id,refresh_token
curl -X POST https://api.invoicebox.ru/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=refresh_token' \
-d 'client_id=my-service' \
-d 'refresh_token=def50200a1b2…'
В ответе приходит новая пара токенов. Прежний refresh_token при этом перестаёт действовать —
храните только последний.
Внимание
Повторное использование уже обменянного refresh_token отзывает всю цепочку: и тот токен, что
предъявили, и тот, что был выдан вместо него. Пользователю придётся заново дать доступ.
Так сделано намеренно. Если старый токен обновления пришёл во второй раз, значит он есть у кого-то ещё, — и правильный ответ здесь не «отказать в одном запросе», а прекратить доступ по всей цепочке. Практический вывод для вас: не обновляйте токен из двух процессов одновременно и не повторяйте запрос обновления при таймауте, не проверив, не пришёл ли ответ.
Отзыв доступа
- метод:
POST - ресурс:
revocation_endpointиз метаданных - тело:
application/x-www-form-urlencoded, параметрыclient_id,token, необязательныйtoken_type_hint(refresh_tokenилиaccess_token)
curl -X POST https://api.invoicebox.ru/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, в том числе на неизвестный или уже отозванный токен. Это требование стандарта:
иначе по ответу можно было бы перебором выяснять, какие токены ещё действуют. Отзыв токена обновления
прекращает доступ и по токенам, выданным его обновлением.
Пользователь со своей стороны отзывает доступ в личном кабинете, и делать для этого ничего не нужно.
Ограничение токена одним сервисом
Параметр resource (RFC 8707) говорит, для какого нашего сервиса нужен токен. Указали — токен
принимается только там; в другом сервисе он не сработает, даже если прав хватает.
Это защита от посредника. Приложение, которому доверили доступ к одному сервису, не должно уметь тем же токеном сходить в другой — а без указанного ресурса токен принимается всюду, где хватает прав.
Значение — адрес сервиса, целиком и без якоря, например https://mcp.invoicebox.ru/mcp. Незнакомый
адрес отклоняется с invalid_target: список известных ресурсов задаём мы, иначе токен получил бы
аудиторию, которую никто не проверяет. Указанный на первом шаге ресурс должен повториться при обмене
кода — иначе приложение получило бы доступ не туда, куда согласился пользователь.
PKCE обязателен
code_challenge и code_verifier нужны всем приложениям, включая серверные с секретом. Причина не в
формальности: код возвращается через браузер пользователя, а секрет приложения в этом обмене не
участвует — перехваченный код без code_verifier бесполезен, с секретом или без.
Поддерживается только метод S256. Приложения, зарегистрированные до этого требования, продолжают
работать по-прежнему, но при обновлении клиента стоит добавить PKCE: включение по одному приложению
делается по вашему запросу.
Ошибки
Отказы приходят по стандарту: код в поле error, пояснение для разработчика — в
error_description.
{
"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 | проверить адрес сервиса |
Читайте также
- Справочник запросов — прежний поток, параметры в camelCase
- Сессия и безопасность — где держать токен, зачем
stateи PKCE - Авторизация запросов к API
- Политики безопасности — хранение токена и что делать при утечке