В системах, где используется сериализация и защита данных на уровне токенов, библиотека Iron применяется для «запечатывания» (seal) и «распечатывания» (unseal) структурированных данных с криптографической защитой. В контексте авторизации токен обычно представляет собой сериализованный объект с метаданными пользователя, временем жизни и контрольной подписью.
Обёртка проверки токена строится как слой абстракции над
iron.unseal, инкапсулирующий:
Такой слой позволяет изолировать криптографическую логику от бизнес-логики приложения.
В основе работы Iron лежит симметричное шифрование с дополнительной защитой целостности данных.
Основные операции:
iron.seal(data, password, options) — преобразует объект
в защищённую строкуiron.unseal(sealed, password, options) —
восстанавливает исходный объектКлючевые параметры:
password — секретный ключttl — время жизни токенаintegrity — алгоритм проверки целостностиencryption — алгоритм шифрованияПример структуры токена:
{
userId: 42,
role: "admin",
iat: 1700000000
}
После применения seal получается строка, содержащая
зашифрованный payload.
Обёртка должна обеспечивать единый контракт проверки токена:
Базовая структура модуля:
import Iron from '@hapi/iron';
class TokenService {
constructor({ password, ttl }) {
this.password = password;
this.ttl = ttl;
}
async verify(token) {
return null;
}
}
export default TokenService;
Первый слой защиты — проверка формата токена до попытки расшифровки.
validateTokenFormat(token) {
if (typeof token !== 'string') {
throw new Error('INVALID_TOKEN_TYPE');
}
if (token.length < 10) {
throw new Error('TOKEN_TOO_SHORT');
}
}
Такая проверка снижает нагрузку на криптографический слой и отсекает очевидно некорректные значения.
Основная логика обёртки строится вокруг Iron.unseal,
который может выбрасывать исключения при:
Инкапсуляция этих ошибок:
async verify(token) {
this.validateTokenFormat(token);
try {
const result = await Iron.unseal(
token,
this.password,
Iron.defaults
);
return {
valid: true,
payload: result
};
} catch (err) {
return {
valid: false,
error: this.normalizeError(err)
};
}
}
Криптографические библиотеки часто возвращают низкоуровневые сообщения, которые нельзя напрямую отдавать наружу.
Создаётся слой нормализации:
normalizeError(err) {
if (err.message.includes('Bad hmac')) {
return 'INVALID_SIGNATURE';
}
if (err.message.includes('Expired')) {
return 'TOKEN_EXPIRED';
}
if (err.message.includes('Malformed')) {
return 'MALFORMED_TOKEN';
}
return 'UNKNOWN_TOKEN_ERROR';
}
Это позволяет стандартизировать поведение системы независимо от внутренней реализации Iron.
Хотя Iron поддерживает TTL внутри unseal, часто
требуется дополнительный контроль на уровне приложения.
checkPayloadValidity(payload) {
if (!payload || typeof payload !== 'object') {
throw new Error('INVALID_PAYLOAD');
}
if (!payload.iat) {
throw new Error('MISSING_IAT');
}
const now = Math.floor(Date.now() / 1000);
if (now - payload.iat > this.ttl) {
throw new Error('TOKEN_EXPIRED');
}
return payload;
}
Эта проверка полезна, если токен мог быть создан вне стандартного механизма или требуется дополнительная бизнес-логика.
Объединённая реализация слоя проверки:
import Iron from '@hapi/iron';
class TokenService {
constructor({ password, ttl }) {
this.password = password;
this.ttl = ttl;
}
validateTokenFormat(token) {
if (typeof token !== 'string') {
throw new Error('INVALID_TOKEN_TYPE');
}
if (token.length < 10) {
throw new Error('TOKEN_TOO_SHORT');
}
}
normalizeError(err) {
if (err.message.includes('Bad hmac')) {
return 'INVALID_SIGNATURE';
}
if (err.message.includes('Expired')) {
return 'TOKEN_EXPIRED';
}
if (err.message.includes('Malformed')) {
return 'MALFORMED_TOKEN';
}
return 'UNKNOWN_TOKEN_ERROR';
}
checkPayloadValidity(payload) {
if (!payload || typeof payload !== 'object') {
throw new Error('INVALID_PAYLOAD');
}
if (!payload.iat) {
throw new Error('MISSING_IAT');
}
const now = Math.floor(Date.now() / 1000);
if (now - payload.iat > this.ttl) {
throw new Error('TOKEN_EXPIRED');
}
return payload;
}
async verify(token) {
try {
this.validateTokenFormat(token);
const payload = await Iron.unseal(
token,
this.password,
Iron.defaults
);
const validated = this.checkPayloadValidity(payload);
return {
valid: true,
payload: validated
};
} catch (err) {
return {
valid: false,
error: this.normalizeError(err)
};
}
}
}
export default TokenService;
Обёртка обычно используется в middleware или сервисе авторизации.
Пример использования:
const tokenService = new TokenService({
password: process.env.IRON_PASSWORD,
ttl: 3600
});
const result = await tokenService.verify(req.headers.authorization);
if (!result.valid) {
return res.status(401).json({ error: result.error });
}
req.user = result.payload;
После базовой проверки токена логично расширить слой авторизации:
hasRole(payload, role) {
return payload.role === role;
}
И использование:
if (!tokenService.hasRole(result.payload, 'admin')) {
return res.status(403).json({ error: 'FORBIDDEN' });
}
При развитии системы структура payload может изменяться. Обёртка позволяет централизованно адаптировать изменения:
Пример поддержки версий:
if (payload.ver !== 2) {
throw new Error('UNSUPPORTED_TOKEN_VERSION');
}
Использование обёртки вокруг Iron позволяет добиться ключевого свойства архитектуры: