Регистрация плагина аутентификации

В типичной серверной архитектуре на Node.js аутентификация строится как отдельный модуль, инкапсулирующий работу с учетными данными, сессиями и токенами. При использовании Iron ключевая идея заключается в защите данных на уровне сериализации: объект пользователя не просто кодируется, а проходит процесс шифрования и подписи, что исключает возможность его изменения на клиентской стороне.

В рамках такого подхода аутентификационный слой обычно состоит из следующих компонентов:

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

Подготовка зависимости Iron

Библиотека Iron из экосистемы Hapi используется для безопасного “запечатывания” (seal) и “распечатывания” (unseal) данных. Она обеспечивает криптографическую защиту объектов, которые могут храниться в cookie или токенах.

Установка:

npm install @hapi/iron

Базовое подключение в модуле:

import Iron from '@hapi/iron';

Основные методы:

  • Iron.seal(data, password, options) — шифрование объекта
  • Iron.unseal(sealed, password, options) — восстановление объекта

Ключи шифрования и политика ротации

Безопасность Iron напрямую зависит от качества секретного ключа. Он должен обладать достаточной энтропией и храниться вне исходного кода.

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

const ironOptions = {
  password: process.env.IRON_PASSWORD,
  encryption: {
    saltBits: 256,
    algorithm: 'aes-256-cbc',
    iterations: 10000
  }
};

Практика ротации ключей включает:

  • периодическую смену password
  • поддержку нескольких активных ключей
  • отказ от жестко закодированных значений

Сериализация учетных данных через Iron

После успешной аутентификации пользовательские данные упаковываются в защищённую строку:

const sessionData = {
  id: user.id,
  role: user.role
};

const sealed = await Iron.seal(sessionData, process.env.IRON_PASSWORD, ironOptions);

При последующих запросах данные извлекаются:

const unsealed = await Iron.unseal(cookieValue, process.env.IRON_PASSWORD, ironOptions);

Ключевой принцип заключается в том, что клиент никогда не видит исходную структуру данных — только зашифрованный контейнер.

Создание плагина аутентификации

В экосистеме Hapi логика аутентификации оформляется как плагин. Он отвечает за:

  • регистрацию стратегии
  • работу с cookie или заголовками
  • валидацию пользователя
  • использование Iron для защиты сессии

Пример структуры плагина:

export const authPlugin = {
  name: 'authPlugin',
  version: '1.0.0',
  register: async function (server, options) {
    
    server.auth.scheme('iron-scheme', () => {
      return {
        authenticate: async (request, h) => {
          const cookie = request.state.session;

          if (!cookie) {
            throw Boom.unauthorized();
          }

          const credentials = await Iron.unseal(
            cookie,
            options.password,
            options.ironOptions
          );

          return h.authenticated({ credentials });
        }
      };
    });
  }
};

Здесь схема iron-scheme определяет поведение проверки каждого запроса.

Регистрация плагина в приложении

После создания плагина он подключается к серверу:

import Hapi from '@hapi/hapi';
import { authPlugin } from './authPlugin.js';

const server = Hapi.server({
  port: 3000
});

await server.register({
  plugin: authPlugin,
  options: {
    password: process.env.IRON_PASSWORD,
    ironOptions: {
      encryption: {
        saltBits: 256,
        algorithm: 'aes-256-cbc',
        iterations: 10000
      }
    }
  }
});

Далее задается стратегия по умолчанию:

server.auth.default('iron-scheme');

Это означает, что каждый маршрут будет защищён, если не указано иное.

Интеграция со стратегиями доступа

После регистрации плагина можно создавать разные уровни доступа. Например:

server.auth.strategy('admin', 'iron-scheme', {
  validate: (credentials) => {
    return { isValid: credentials.role === 'admin' };
  }
});

Использование в маршруте:

server.route({
  method: 'GET',
  path: '/admin',
  options: {
    auth: 'admin'
  },
  handler: (request) => {
    return { status: 'ok' };
  }
});

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

Обработка ошибок и безопасность

При работе с Iron важно учитывать потенциальные точки отказа:

  • поврежденные или подмененные cookie
  • использование устаревшего ключа
  • попытки повторного использования токена

Типовая обработка ошибок:

try {
  const data = await Iron.unseal(token, password, options);
} catch (err) {
  throw Boom.unauthorized('Invalid session');
}

Рекомендуемые меры безопасности:

  • ограничение времени жизни сессии через дополнительные поля payload
  • привязка токена к user-agent или IP (с осторожностью)
  • строгая политика CORS и secure cookies
  • обязательное использование HTTPS

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