Библиотека @hapi/iron реализует механизм сериализации и
криптографической защиты данных, известный как seal/unseal. В
отличие от классических JWT, где структура токена читаема и проверяется
по подписи, Iron создаёт полностью зашифрованную и подписанную строку,
содержащую произвольный объект.
Основная идея заключается в том, что токен не просто подписывается, а:
Пример базовой операции:
import Iron fr om '@hapi/iron';
const password = 'super-secure-password';
const data = {
userId: 123,
role: 'admin',
iat: Date.now()
};
const sealed = await Iron.seal(data, password, Iron.defaults);
const unsealed = await Iron.unseal(sealed, password, Iron.defaults);
Таким образом, сервер может выдавать клиенту полностью автономный токен, внутри которого хранится вся необходимая информация.
При использовании Iron (как и любых «самодостаточных» токенов) возникает фундаментальная проблема: после выдачи токен становится независимым от сервера.
Это означает:
Отсюда возникает задача: как сделать так, чтобы токен можно было инвалидировать без использования чёрного списка (blacklist).
Чёрный список в классическом виде требует хранения всех отозванных токенов, что:
Iron не предполагает встроенного механизма ревокации. Он работает как криптографический контейнер:
Поэтому инвалидирование реализуется не через сам токен, а через изменение условий его валидности.
Самый простой способ уменьшить необходимость отзыва токенов — ограничить их TTL.
const sealed = await Iron.seal(data, password, {
...Iron.defaults,
ttl: 5 * 60 * 1000 // 5 минут
});
Идея:
Однако этого недостаточно для систем с выходом пользователя, сменой пароля или блокировкой аккаунта.
Один из наиболее распространённых подходов — хранение версии токена в данных пользователя.
В токен добавляется поле:
{
userId: 123,
tokenVersion: 7
}
В базе данных хранится:
users:
- id: 123
- token_version: 8
const session = await Iron.unseal(token, password, Iron.defaults);
const user = await db.users.findById(session.userId);
if (session.tokenVersion !== user.token_version) {
throw new Error('Token invalid');
}
Во всех случаях:
UPD ATE users SE T token_version = token_version + 1 WH ERE id = 123;
Все старые токены автоматически становятся недействительными без хранения списка.
Вместо версии можно использовать временную метку глобальной сессии пользователя.
{
userId: 123,
sessionEpoch: 1700000000000
}
В базе:
users:
- id: 123
- session_epoch: 1705000000000
При проверке:
if (session.sessionEpoch < user.session_epoch) {
throw new Error('Session expired');
}
Поскольку Iron использует симметричный ключ (password),
смена этого ключа приводит к автоматической инвалидизации всех
токенов.
const oldPassword = 'v1-secret';
const newPassword = 'v2-secret';
После смены:
Это «грубый» механизм:
Можно эмулировать версионирование ключей:
{
userId: 123,
kid: 'key-2026-01'
}
На сервере:
const keys = {
'key-2025-12': 'old-password',
'key-2026-01': 'new-password'
};
const password = keys[session.kid];
Для операций повышенной безопасности можно вводить счётчик использования.
{
userId: 123,
nonce: 42
}
В базе:
users:
- id: 123
- nonce: 43
Проверка:
if (session.nonce !== user.nonce) {
throw new Error('Token reused or outdated');
}
Дополнительный уровень — привязка токена к окружению:
{
userId: 123,
deviceHash: 'a1b2c3',
ipSegment: '192.168'
}
Проверка:
if (session.deviceHash !== currentDeviceHash) {
throw new Error('Device mismatch');
}
Этот подход не заменяет инвалидирование, но снижает ценность украденного токена.
Комбинация Iron и короткого TTL позволяет полностью отказаться от хранения состояния:
Пример логики:
tokenVersionСистема инвалидирования может быть сведена к нескольким параметрам:
tokenVersion — точечная инвалидизацияsessionEpoch — массовая инвалидизацияkid — управление ключамиВсе механизмы работают без хранения списка «плохих токенов».
async function verifyToken(token, userId) {
const session = await Iron.unseal(token, password, Iron.defaults);
const user = await db.users.findById(userId);
if (!user) throw new Error('User not found');
if (session.userId !== user.id) {
throw new Error('Invalid token payload');
}
if (session.tokenVersion !== user.token_version) {
throw new Error('Token revoked');
}
if (session.sessionEpoch < user.session_epoch) {
throw new Error('Session expired');
}
return session;
}
TTL Минимальная сложность, слабая гибкость
tokenVersion Точная и контролируемая инвалидизация
sessionEpoch Массовая инвалидизация без перебора токенов
rotation password Глобальный сброс всех сессий
kid-based keys Управление жизненным циклом криптографических ключей
В реальных системах Iron чаще всего используется не изолированно, а как часть гибридной архитектуры:
Такой подход позволяет полностью отказаться от чёрного списка, сохраняя контроль над сессиями через минимальные, дешёвые проверки состояния.