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

Авторизация

Pert API использует JWT-токены в заголовке Authorization. Сам токен выдаётся в обмен на запрос, подписанный вашим Ed25519-секретным ключом — без передачи секрета по сети.

Получение токена «end-to-end» с примерами кода описано в разделе Начало работы. Этот раздел — справочник: что входит в подписываемый payload, как валидируется запрос, сколько живёт токен, как его обновлять и проверять.

Заголовки запросов

Заголовок Значение Когда нужен
Authorization Bearer <ACCESS_TOKEN> Все эндпоинты, кроме /api/v1/auth/access-token и /api/v1/auth/time
X-Workspace-Id UUID workspace Все эндпоинты, работающие в контексте workspace (vault, wallet, transaction, …)

Workspace выбирается заголовком

Токен может содержать доступы сразу к нескольким workspace. Конкретный контекст для каждого запроса определяется заголовком X-Workspace-Id — если в токене нет доступов к этому workspace, запрос завершится ошибкой 403.

Получение access token

POST /api/v1/auth/access-token

Подписываемый payload

JSON-объект со следующими полями:

Поле Тип Обязательное Описание
client_type string да userAPI-пользователь
client_id string да UUID API-пользователя
timestamp int64 да Unix-время в секундах (см. Серверное время)
nonce string да Уникальная случайная строка (≥16 байт энтропии). Гарантирует уникальность подписи

Сервер валидирует payload:

  • timestamp должен быть не старше 5 минут и не более чем на 1 минуту в будущем относительно серверного времени;
  • nonce обязателен и не должен быть пустым;
  • client_id должен соответствовать активному API-пользователю в указанной роли.

Тело запроса

Поле Тип Описание
data string base64-кодированный JSON payload
signature string base64-кодированная Ed25519-подпись сырых байт JSON (до base64-кодирования)

Что именно подписывается

Подпись считается над байтами JSON до base64-кодирования. Не подписывайте base64-строку и не меняйте сериализацию JSON (порядок полей, пробелы) после подписи — проверка на сервере сломается.

Ответ

{
  "access_token": "eyJhbGciOi...",
  "expires_at":   "1777300943056"
}
Поле Тип Описание
access_token string JWT, передавайте в заголовке Authorization: Bearer …
expires_at string Время истечения токена в миллисекундах Unix (строкой)

Серверное время

GET /api/v1/auth/time

Эндпоинт открыт без авторизации и возвращает текущее серверное время:

{
  "unix_seconds":      "1777300943",
  "unix_milliseconds": "1777300943056"
}

Когда использовать

Если ваш хост может расходиться по времени с сервером больше, чем на минуту, запрашивайте серверное время перед каждой генерацией подписи и берите unix_seconds как timestamp payload. Это исключает отказы из-за clock drift.

TTL и обновление токена

  • Срок жизни access token, выданного по публичному ключу — 1 час.
  • Refresh-токен не выдаётся: для обновления повторите процедуру подписи и получите новый access token.
  • Поле expires_at (миллисекунды Unix) — единственный источник истины по времени истечения.

Рекомендуемая стратегия обновления:

  1. Сохраните access_token и expires_at в памяти приложения.
  2. Перед каждым запросом сверяйте: если до expires_at осталось ≤ 30 секунд — обновите токен.
  3. На ответ 401 Unauthorized от любого эндпоинта — обновите токен и повторите запрос один раз.

Не запрашивайте новый токен на каждый вызов

Каждый вызов /api/v1/auth/access-token — это сетевой round-trip и подпись на вашей стороне. На высоконагруженных интеграциях это превращается в узкое место. Кешируйте токен и переиспользуйте до истечения.

Что внутри токена

Access token — это подписанный EdDSA (Ed25519) JWT. Полезная нагрузка содержит идентификатор субъекта и массив доступов accesses — по одному элементу на каждый workspace, к которому у API-пользователя есть роль:

Поле в access Описание
workspace_uuid UUID workspace
role Роль API-пользователя в этом workspace (ROLE_SIGNER, ROLE_VIEWER, ROLE_APPROVER, …)
permissions Набор разрешений роли
role_status Статус роли (ROLE_STATUS_ACTIVE для рабочей роли)

Множественные workspace

Если API-пользователю выдана роль в нескольких workspace одного аккаунта, один токен покрывает их все. Контекст конкретного запроса определяется заголовком X-Workspace-Id.

Проверка токена: introspect

POST /api/v1/auth/introspect

Возвращает детали по текущему access token: статус активности, время выдачи и истечения, субъект и список доступов. Полезно для отладки интеграции и для администраторских сценариев — на горячем пути авторизации использовать не нужно (вся информация уже есть в самом JWT).

curl -X POST https://auth.pert.paranoid.security/api/v1/auth/introspect \
    -H "Authorization: Bearer $ACCESS_TOKEN"

Ответ:

{
  "active":       true,
  "subject_type": "user",
  "subject_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "issued_at":    "1777299043056",
  "expires_at":   "1777300943056",
  "jti":          "f1b2…",
  "accesses": [
    {
      "workspace_uuid": "a1b2…",
      "role":           "ROLE_SIGNER",
      "permissions":    ["TRANSACTION_CREATE", "TRANSACTION_APPROVE", "..."],
      "role_status":    "ROLE_STATUS_ACTIVE"
    }
  ]
}

Ошибки

HTTP Причина Что делать
400 Невалидный формат payload, timestamp вне допустимого окна, пустой nonce Сверьтесь с серверным временем, проверьте JSON-сериализацию
401 Невалидная подпись, истёкший токен, отсутствует Authorization Перегенерируйте подпись/токен. Убедитесь, что подписываете сырой JSON, а не base64
403 Нет доступа к workspace в X-Workspace-Id или у роли нет нужного permission Проверьте accesses через introspect; запросите нужные права у владельца workspace
404 API-пользователь не найден или деактивирован Проверьте client_id и статус пользователя в консоли

Безопасность приватного ключа

  • Хранилище: secrets-менеджер (Vault, AWS/GCP/Azure Secrets Manager, KMS), не репозиторий и не env-файл в образе.
  • Доступ: только тот процесс, который реально подписывает запросы. Никаких chmod 644.
  • Ротация: создайте нового API-пользователя с новой парой ключей, переключите интеграцию, затем деактивируйте старого. Не используйте один client_id с двумя ключами.
  • Компрометация: немедленно деактивируйте API-пользователя в консоли. Все ранее выданные токены продолжают работать до expires_at (максимум 1 час), новые получить уже не получится.

Один API-пользователь — одна интеграция

Не используйте один client_id/секрет в нескольких разных сервисах. При компрометации одного отзыв затронет всех — а аудит-лог не различит источник запросов.