Временные одноразовые токены (One-Time Tokens, OTT) используются для повышения безопасности взаимодействия между клиентом и сервером. Они позволяют:
В контексте библиотеки Iron такие токены строятся на основе криптографически защищённых контейнеров (sealing), которые включают полезную нагрузку и метаданные, защищённые шифрованием и проверкой целостности.
Iron реализует механизм seal/unseal, где:
seal — сериализует и шифрует объектunseal — расшифровывает и валидирует данныеКаждый токен содержит:
Одноразовость достигается за счёт:
import Iron from '@hapi/iron';
const password = 'super-secret-key';
async function generateToken(data) {
const sealed = await Iron.seal(data, password, Iron.defaults);
return sealed;
}
Особенности:
data — любой сериализуемый объектpassword — криптографический ключIron.defaults — параметры безопасности (можно
кастомизировать)async function verifyToken(token) {
try {
const unsealed = await Iron.unseal(token, password, Iron.defaults);
return unsealed;
} catch (err) {
return null;
}
}
Если токен:
→ будет выброшено исключение
Iron поддерживает встроенный механизм TTL:
const options = {
...Iron.defaults,
ttl: 60 * 1000 // 1 минута
};
const token = await Iron.seal({ userId: 123 }, password, options);
При попытке расшифровки:
await Iron.unseal(token, password, options);
если время истекло — токен считается недействительным.
Iron сам по себе не отслеживает, использовался ли токен ранее. Для полной одноразовости требуется дополнительная логика.
import { v4 as uuidv4 } from 'uuid';
const usedTokens = new Set();
async function createOneTimeToken(data) {
const nonce = uuidv4();
const token = await Iron.seal(
{ ...data, nonce },
password,
Iron.defaults
);
return token;
}
Проверка:
async function consumeToken(token) {
const data = await Iron.unseal(token, password, Iron.defaults);
if (usedTokens.has(data.nonce)) {
throw new Error('Token already used');
}
usedTokens.add(data.nonce);
return data;
}
Для распределённых систем:
import Redis from 'ioredis';
const redis = new Redis();
async function consumeToken(token) {
const data = await Iron.unseal(token, password, Iron.defaults);
const exists = await redis.get(data.nonce);
if (exists) {
throw new Error('Token already used');
}
await redis.set(data.nonce, '1', 'EX', 60);
return data;
}
Преимущества:
Рекомендуется включать:
{
userId: 123,
action: 'password_reset',
nonce: 'uuid',
issuedAt: Date.now()
}
Ключевые элементы:
userId — идентификатор пользователяaction — тип операцииnonce — уникальностьissuedAt — дополнительная проверка времениДаже при коротком TTL возможна атака повтором. Поэтому:
Iron позволяет гибко управлять алгоритмами:
const options = {
encryption: {
algorithm: 'aes-256-cbc',
saltBits: 256,
iterations: 10000
},
integrity: {
algorithm: 'sha256',
saltBits: 256,
iterations: 10000
},
ttl: 60000
};
Важно:
iterations повышает безопасностьГенерация:
async function createResetToken(userId) {
return await Iron.seal(
{
userId,
action: 'reset_password',
nonce: uuidv4()
},
password,
{ ...Iron.defaults, ttl: 15 * 60 * 1000 }
);
}
Проверка:
async function validateResetToken(token) {
const data = await Iron.unseal(token, password, Iron.defaults);
if (data.action !== 'reset_password') {
throw new Error('Invalid token type');
}
// Проверка nonce в хранилище
return data.userId;
}
Токен отправляется пользователю:
const token = await Iron.seal(
{
userId: 123,
action: 'login',
nonce: uuidv4()
},
password,
{ ...Iron.defaults, ttl: 5 * 60 * 1000 }
);
Ссылка:
https://example.com/login?token=...
После перехода:
1. Отсутствие nonce → токен можно использовать повторно
2. Слишком большой TTL → увеличивает окно атаки
3. Использование слабого пароля → риск расшифровки токенов
4. Отсутствие проверки действия
(action) → токен может использоваться не по
назначению
5. Отсутствие централизованного хранилища → одноразовость нарушается в кластерной среде
action)При работе в нескольких инстансах:
Операции Iron включают:
Это делает их:
Оптимизации:
iterations при высокой нагрузке1. Подписание API-запросов
Каждый запрос содержит одноразовый токен:
2. Подтверждение операций
Например:
3. Межсервисная аутентификация
| Характеристика | Iron | JWT |
|---|---|---|
| Шифрование | Да | Обычно нет |
| Подпись | Да | Да |
| Одноразовость | Нужно реализовать | Нужно реализовать |
| Простота | Средняя | Высокая |
| Безопасность payload | Высокая (зашифрован) | Низкая (base64) |
Iron предпочтителен, когда:
Проверяются:
Пример:
test('token expires', async () => {
const token = await generateToken({ a: 1 });
await new Promise(r => setTimeout(r, 2000));
await expect(verifyToken(token)).rejects.toThrow();
});
Временные одноразовые токены в Iron обеспечивают:
При правильной реализации становятся мощным инструментом для безопасных одноразовых операций в веб-приложениях.