Перейти к содержанию

Авторизация внешних интеграций

Назначение

Этот документ описывает, как работает machine-to-machine авторизация для внешних интеграций.

Один integration client может работать с одной или несколькими организациями, если такой доступ разрешен на стороне сервера.

Термины

  • integration client: серверная запись с clientId и clientSecret
  • доступ к организации: allowlist-запись, которая разрешает конкретному integration client работать с конкретной организацией
  • technical user: внутренний технический пользователь, которого система создает для конкретной пары организация + integration client
  • X-Organization-Id: HTTP-заголовок, через который выбирается контекст организации для org-scoped запросов

Общая схема работы

  1. Внешней системе выдаются clientId и clientSecret.
  2. Внешняя система получает access token через POST /api/account/token.
  3. Внешняя система вызывает API с заголовком Authorization: Bearer <access_token>.
  4. Для методов, работающих в контексте организации, дополнительно передается X-Organization-Id.
  5. Сервер проверяет, что integration client имеет доступ к указанной организации. Для всех новых организаций доступ автоматически есть.
  6. Если доступ разрешен, сервер автоматически переключает выполнение на technical user этой организации.

Внешняя система не передает userId и не выбирает внутреннего пользователя вручную.

Получение токена

Запрос

POST /api/account/token

Content-Type: application/json

{
  "grantType": "client_credentials",
  "clientId": "your-client-id",
  "clientSecret": "your-client-secret"
}

Важно

Текущая реализация ожидает JSON body.

Ответ

{
  "accessToken": {
    "token": "<jwt>",
    "expiresIn": 3600
  },
  "refreshToken": null,
  "currentUser": null,
  "requiresTwoFactor": false,
  "twoFactorProvider": null,
  "twoFactorToken": null
}

Важно

  • refreshToken для machine-to-machine авторизации не используется
  • новый access token нужно получать повторным запросом с теми же clientId и clientSecret

Семантика токена

Выданный JWT идентифицирует integration client, а не бизнес-пользователя.

Это означает:

  • сам токен не привязан к одной организации
  • один и тот же токен можно использовать для разных организаций, если у integration client есть доступ к ним
  • контекст организации выбирается на каждый запрос отдельно через X-Organization-Id

Внешней системе нужно воспринимать access token как непрозрачный токен. Не нужно строить бизнес-логику на внутренних JWT claims.

X-Organization-Id

Когда заголовок обязателен

X-Organization-Id обязателен для любого запроса, который должен выполняться в контексте конкретной организации.

Пример:

X-Organization-Id: 12345

Что делает сервер

Если в запросе передан X-Organization-Id, сервер:

  1. читает clientId из machine token
  2. проверяет, что integration client имеет доступ к указанной организации
  3. заменяет текущий principal на этого technical user
  4. передает дальше в систему уже обычные внутренние userId и organizationId

За счет этого обычная бизнес-логика, валидаторы работают так, как будто запрос выполнил внутренний пользователь организации.

Что будет, если заголовок не передать

Для org-scoped integration endpoints запрос будет отклонен с 401 Unauthorized.

Типовые причины:

  • не передан X-Organization-Id
  • передан некорректный X-Organization-Id

Модель доступа

Доступ контролируется двумя серверными сущностями:

  • IntegrationClient: хранит clientId и clientSecret
  • OrganizationIntegrationClient: allowlist-запись, которая связывает integration client с конкретной организацией

Если для пары clientId + organizationId нет allowlist-записи, org-scoped запросы будут отклоняться.

Метод создания организации

Endpoint

POST /api/integrations/organizations/apply

Авторизация

  • обязателен Authorization: Bearer <access_token>
  • X-Organization-Id для этого метода не нужен

Request body

{
  "name": "ООО Ромашка",
  "inn": "7701234567",
  "kpp": "770101001",
  "ogrn": "1027700000000"
}

Все поля опциональны, кроме тех случаев, когда они обязательны по бизнес-правилам на стороне клиента. Сервер выполняет валидацию формата inn и kpp, если они переданы.

Поведение

Если передан inn:

  • сервер ищет существующую организацию по inn
  • если организация уже есть, возвращается ее существующий идентификатор
  • если организации нет, создается новая организация и возвращается ее идентификатор

Если inn не передан:

  • сервер использует специальную организацию-отстойник
  • если организация-отстойник еще не существует, она создается автоматически
  • возвращается идентификатор этой организации

В обоих случаях сервер также гарантирует, что:

  • integration client связан с организацией
  • для пары organization + integration client существует technical user
  • для этого technical user существует SyncSystem с Behavior = Read

Ответ

{
  "id": 12345
}

Вызов других org-scoped API

Тот же machine token можно использовать и для других API-методов, которые зависят от текущего внутреннего пользователя и организации, если:

  • integration client имеет доступ к целевой организации
  • передан X-Organization-Id
  • целевой метод доступен ролям technical user

Для таких запросов внешняя система должна вызывать нужный API обычным способом и не должна самостоятельно передавать внутренний userId, если только этого явно не требует контракт конкретного метода.

Чеклист интеграции

  1. Получить clientId и clientSecret из CARGO.RUN от разработчиков.
  2. Запросить access token через POST /api/account/token.
  3. Безопасно хранить access token и запрашивать новый токен повторно до или после истечения срока действия.
  4. Для создания организации вызывать POST /api/integrations/organizations/apply без X-Organization-Id.
  5. Сохранить возвращенный organizationId.
  6. Для любого org-scoped метода передавать X-Organization-Id с этим идентификатором.
  7. Использовать один и тот же access token для нескольких организаций только в том случае, если на сервере для этого выдан allowlist-доступ.

Примечания

  • внешней системе не нужно ожидать, что в JWT claims будет бизнесовый userId
  • контекст организации выбирается заголовком, а не самим токеном