Обработка повреждённых токенов

В контексте использования Iron токен представляет собой зашифрованную и подписанную структуру, которая после сериализации проходит несколько стадий преобразования: сериализация данных, шифрование, добавление HMAC-подписи и кодирование в строку. Любое нарушение на одном из этапов приводит к невозможности корректного восстановления исходного объекта.

На практике повреждение токена возникает по следующим причинам:

  • изменение строки токена при передаче (обрезка, вставка символов)
  • ошибки кодировки при хранении (UTF-8 ↔︎ Base64)
  • подмена токена в результате атаки или прокси-интерференции
  • использование устаревшего или несовместимого секретного ключа
  • повреждение данных в cookies или localStorage
  • некорректная сериализация объекта перед упаковкой

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


Поведение Iron при повреждённых токенах

Библиотека @hapi/iron не пытается «восстановить» повреждённые данные. Любая ошибка целостности приводит к выбросу исключения при попытке распаковки.

Основные классы ошибок:

  • Bad HMAC Value — нарушена подпись, данные были изменены
  • Decryption failed — невозможность расшифровки содержимого
  • Bad version number — несовместимый формат токена
  • Invalid structure — токен повреждён или обрезан
  • Bad input — некорректная строка на входе

Эта модель поведения является частью безопасности: отсутствие «частичного восстановления» предотвращает использование потенциально модифицированных данных.


Базовая обработка ошибок при распаковке

Любая операция извлечения данных из токена должна быть защищена конструкцией try/catch, поскольку Iron работает через исключения.

import Iron from '@hapi/iron';

const password = 'very-secure-password';

async function decodeToken(sealed) {
  try {
    const unsealed = await Iron.unseal(
      sealed,
      password,
      Iron.defaults
    );

    return unsealed;
  } catch (err) {
    return null;
  }
}

В данном случае любое повреждение токена приводит к безопасному возврату null, без раскрытия внутренних деталей ошибки.


Дифференциация типов ошибок

Более строгая обработка требует различения причин сбоя. Iron выбрасывает стандартные ошибки, которые можно анализировать по err.message.

async function decodeToken(sealed) {
  try {
    return await Iron.unseal(sealed, password, Iron.defaults);
  } catch (err) {
    switch (err.message) {
      case 'Bad HMAC Value':
        // возможная подмена токена
        break;

      case 'Decryption failed':
        // повреждение или неверный ключ
        break;

      case 'Bad version number':
        // устаревший формат
        break;

      default:
        // неизвестное повреждение
        break;
    }

    return null;
  }
}

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


Защита от повторного использования повреждённых токенов

Повреждённые токены не всегда случайны. В ряде случаев они могут быть результатом попытки повторной отправки модифицированных данных. Поэтому важно минимизировать возможность их повторного анализа.

Практика:

  • не логировать содержимое токена
  • не возвращать детали ошибки клиенту
  • использовать единый ответ для всех типов сбоев
catch (err) {
  // логирование только на сервере
  console.error('Token error');

  return null;
}

Обработка токенов в HTTP-контексте

При использовании Iron для сессий через cookies повреждённый токен часто приходит от клиента автоматически. В этом случае обработка должна быть прозрачной для пользователя.

export async function authMiddleware(req, res, next) {
  const token = req.cookies.session;

  if (!token) {
    req.user = null;
    return next();
  }

  try {
    req.user = await Iron.unseal(token, password, Iron.defaults);
  } catch {
    req.user = null;

    res.clearCookie('session');
  }

  next();
}

Удаление повреждённого токена предотвращает бесконечные циклы ошибок при повторных запросах.


Проверка целостности до распаковки

Хотя Iron уже выполняет проверку целостности, предварительная валидация строки снижает количество лишних исключений.

function isLikelyToken(str) {
  return typeof str === 'string' &&
    str.length > 20 &&
    /^[A-Za-z0-9\-_\.]+$/.test(str);
}

Такая фильтрация не гарантирует валидность, но отсеивает явно испорченные данные до вызова unseal.


Работа с массовыми повреждениями

В реальных системах возможны массовые сбои токенов после:

  • смены ключа шифрования
  • обновления алгоритма Iron
  • миграции формата сессий

В таких случаях корректная стратегия — полная инвалидизация сессий.

async function safeUnseal(sealed) {
  try {
    return await Iron.unseal(sealed, newPassword, Iron.defaults);
  } catch {
    return null;
  }
}

При этом клиентская часть должна быть рассчитана на повторную авторизацию без восстановления старого состояния.


Логирование и диагностика

Хотя детали ошибок не должны передаваться наружу, серверная диагностика остаётся важной частью эксплуатации.

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

catch (err) {
  logger.error({
    type: 'iron_token_error',
    message: err.message,
    stack: err.stack
  });

  return null;
}

При этом важно избегать сохранения самого токена в логах, так как он может содержать чувствительные данные даже в зашифрованном виде.


Защита от скрытой модификации данных

Iron гарантирует целостность, но не защищает от логических ошибок приложения. Например, корректный токен может содержать устаревшие данные пользователя.

Поэтому после успешной распаковки часто добавляется дополнительная проверка:

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

if (session.exp && Date.now() > session.exp) {
  return null;
}

Таким образом разделяются два уровня проверки:

  • криптографическая целостность (Iron)
  • бизнес-валидность (приложение)

Поведение при некорректных ключах

Смена или рассинхронизация секретного ключа приводит к массовому появлению ошибок Bad HMAC Value и Decryption failed. В этом случае все ранее выданные токены становятся недействительными.

Типичная реакция системы:

  • автоматическое очищение cookies
  • принудительный logout
  • регенерация сессии
catch (err) {
  if (err.message === 'Bad HMAC Value') {
    res.clearCookie('session');
  }

  return null;
}

Обработка частично повреждённых данных

Иногда токен формально проходит проверку структуры, но данные внутри оказываются некорректными из-за ошибок сериализации.

Пример защиты:

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

if (!session || typeof session !== 'object') {
  return null;
}

if (!session.userId) {
  return null;
}

Iron гарантирует криптографическую корректность, но не структуру бизнес-данных.


Устойчивость системы к ошибкам токенов

Стабильная работа с Iron в условиях повреждённых токенов строится на нескольких принципах:

  • отсутствие доверия к входным данным до распаковки
  • обязательная обработка исключений
  • единое поведение при любых ошибках
  • минимизация утечки информации через ошибки
  • очистка некорректных сессий на стороне сервера

Такая модель делает систему устойчивой к случайным повреждениям и целенаправленным попыткам вмешательства в структуру токенов.