Токены с ограниченными правами (scoped tokens)

Токены с ограниченными правами (scoped tokens) — это механизм контроля доступа, при котором выданный токен содержит не только факт аутентификации, но и строго определённый набор разрешений (scope). В отличие от «полных» токенов, такие токены позволяют выполнять лишь ограничённый набор операций, что существенно снижает риски безопасности.

В библиотеке Iron для JavaScript scoped tokens реализуются через шифрование полезной нагрузки (payload), в которую включаются данные о правах доступа, сроке действия и дополнительных ограничениях.


Зачем нужны ограничения прав

Использование scoped tokens решает несколько ключевых задач:

  • Принцип наименьших привилегий — токен получает только те права, которые необходимы для конкретной операции
  • Безопасность API — даже при утечке токена злоумышленник не сможет выполнить критические действия
  • Гибкость архитектуры — можно выдавать разные токены для разных сервисов и сценариев
  • Контроль времени жизни — scoped tokens часто имеют короткий срок действия

Структура токена в Iron

Iron не использует JWT в чистом виде, но концептуально структура похожа. Токен представляет собой зашифрованную строку, содержащую:

  • полезную нагрузку (payload)
  • метаданные (например, timestamp)
  • криптографическую подпись

Пример payload для scoped token:

{
  "userId": 123,
  "scope": ["read:profile", "edit:settings"],
  "expiresAt": 1715000000000
}

Создание scoped token

Для работы с Iron используется пакет @hapi/iron.

Пример генерации токена:

import Iron from '@hapi/iron';

const password = 'super-secure-password';

const payload = {
  userId: 123,
  scope: ['read:data'],
  expiresAt: Date.now() + 1000 * 60 * 10 // 10 минут
};

const token = await Iron.seal(payload, password, Iron.defaults);

console.log(token);

Ключевые моменты:

  • seal шифрует данные и возвращает токен
  • пароль должен быть достаточно сложным
  • payload полностью контролируется разработчиком

Расшифровка и проверка токена

const unsealed = await Iron.unseal(token, password, Iron.defaults);

if (unsealed.expiresAt < Date.now()) {
  throw new Error('Token expired');
}

console.log(unsealed.scope);

Важно:

  • Iron не проверяет срок действия автоматически
  • вся логика валидации — на стороне приложения

Реализация проверки scope

Scoped tokens бесполезны без строгой проверки прав. Обычно реализуется middleware:

function requireScope(requiredScope) {
  return (req, res, next) => {
    const token = req.headers.authorization;

    Iron.unseal(token, password, Iron.defaults)
      .then(data => {
        if (!data.scope.includes(requiredScope)) {
          return res.status(403).send('Forbidden');
        }

        req.user = data;
        next();
      })
      .catch(() => res.status(401).send('Unauthorized'));
  };
}

Применение:

app.get('/profile', requireScope('read:profile'), (req, res) => {
  res.send('Profile data');
});

Иерархия и композиция прав

Scopes удобно строить по принципу иерархии:

  • read:* — доступ ко всем операциям чтения
  • write:* — доступ ко всем операциям записи
  • admin:* — полный доступ

Пример расширенной проверки:

function hasScope(userScopes, required) {
  return userScopes.includes(required) ||
         userScopes.includes(required.split(':')[0] + ':*') ||
         userScopes.includes('*');
}

Ограничение по контексту

Scoped tokens могут содержать дополнительные ограничения:

По ресурсу

{
  "scope": ["read:file"],
  "resourceId": "file_123"
}

Проверка:

if (data.resourceId !== requestedId) {
  throw new Error('Access denied');
}

По IP

{
  "ip": "192.168.1.1"
}

По устройству

{
  "device": "mobile"
}

Краткоживущие токены

Scoped tokens часто используются как временные ключи доступа:

  • загрузка файла
  • доступ к API третьей стороны
  • одноразовые действия

Пример:

const payload = {
  action: 'upload',
  scope: ['upload:file'],
  expiresAt: Date.now() + 1000 * 60 // 1 минута
};

Делегирование прав

Scoped tokens позволяют делегировать права от одного сервиса другому.

Сценарий:

  1. Основной сервис создаёт токен с ограниченным scope
  2. Передаёт его клиенту или другому сервису
  3. Получатель может выполнить только разрешённые действия

Пример:

const delegatedToken = await Iron.seal({
  scope: ['read:public-data'],
  issuedBy: 'service-A'
}, password, Iron.defaults);

Ротация и отзыв токенов

Iron не хранит токены на сервере, поэтому отзыв реализуется через:

  • сокращение времени жизни
  • использование версии токена

Пример:

{
  "userId": 123,
  "scope": ["read"],
  "version": 2
}

Проверка:

if (data.version !== currentUserTokenVersion) {
  throw new Error('Token revoked');
}

Защита от подмены scope

Так как payload зашифрован, изменить scope невозможно без знания пароля. Однако важно:

  • не использовать слабые пароли
  • не передавать пароль клиенту
  • использовать разные пароли для разных типов токенов

Разделение токенов по назначению

Рекомендуется использовать разные конфигурации:

  • токены аутентификации
  • токены API-доступа
  • временные scoped tokens

Пример:

const authPassword = 'auth-secret';
const apiPassword = 'api-secret';

Практический пример: доступ к файлу

Генерация токена:

const token = await Iron.seal({
  fileId: 'file_123',
  scope: ['read:file'],
  expiresAt: Date.now() + 300000
}, password, Iron.defaults);

Проверка:

const data = await Iron.unseal(token, password, Iron.defaults);

if (!data.scope.includes('read:file')) {
  throw new Error('No permission');
}

if (data.fileId !== requestedFileId) {
  throw new Error('Wrong file');
}

Ошибки при работе со scoped tokens

Наиболее распространённые проблемы:

  • отсутствие проверки срока действия
  • чрезмерно широкие scope (*)
  • использование одного секрета для всех токенов
  • доверие данным без повторной валидации
  • хранение токенов в небезопасных местах

Производительность и накладные расходы

Iron использует криптографические операции:

  • шифрование (AES)
  • подпись (HMAC)

Это делает токены:

  • более безопасными, чем plain JSON
  • но тяжелее по вычислениям

Для оптимизации:

  • избегать слишком больших payload
  • использовать кэширование результатов валидации при необходимости

Расширение модели scope

Сложные системы используют:

  • RBAC (Role-Based Access Control)
  • ABAC (Attribute-Based Access Control)

Пример расширенного payload:

{
  "roles": ["editor"],
  "permissions": ["edit:article"],
  "attributes": {
    "department": "media"
  }
}

Сравнение с JWT

Характеристика Iron JWT
Шифрование Да (по умолчанию) Нет (обычно только подпись)
Читаемость Нет Да
Безопасность payload Высокая Средняя
Scoped tokens Реализуются вручную Через claims

Архитектурные рекомендации

  • использовать короткоживущие scoped tokens
  • минимизировать scope
  • валидировать каждый запрос
  • разделять секреты
  • логировать попытки доступа

Итоговая модель использования

  1. Генерация токена с минимальным scope
  2. Передача токена клиенту или сервису
  3. Расшифровка и проверка на сервере
  4. Контроль scope, времени и контекста
  5. Выполнение разрешённой операции

Scoped tokens в Iron позволяют строить гибкую, безопасную систему авторизации без необходимости хранения сессий на сервере, при этом сохраняя полный контроль над содержимым и логикой проверки токена.