Регистрация схемы через server.auth.scheme

Механизм server.auth.scheme в Hapi используется для регистрации низкоуровневого способа аутентификации. Схема определяет поведение проверки учетных данных, но не привязывается к конкретной стратегии использования. Это слой, на котором описывается логика проверки запроса, извлечения и валидации данных, а также формирования результата аутентификации.

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


Регистрация схемы через server.auth.scheme

Регистрация схемы выполняется через метод:

server.auth.scheme(name, schemeFactory);
  • name — уникальное имя схемы
  • schemeFactory — функция, возвращающая объект с методом authenticate

Фабрика схемы вызывается один раз при регистрации, и должна вернуть объект, реализующий интерфейс аутентификации.

Минимальная структура:

server.auth.scheme('custom', (server, options) => {
    return {
        authenticate: async (request, h) => {
            return h.authenticated({ credentials: {} });
        }
    };
});

Фабрика схемы и контекст сервера

Фабрика схемы получает доступ к серверу и опциям:

(server, options) => { }
  • server позволяет использовать конфигурацию, логирование, методы криптографии
  • options содержит параметры стратегии, переданные при создании

Это позволяет отделить реализацию схемы от конкретных параметров окружения.


Структура метода authenticate

Ключевой частью схемы является метод authenticate:

authenticate: async (request, h) => { }

Он должен:

  1. Извлечь данные аутентификации из запроса
  2. Проверить их корректность
  3. Вернуть результат через h.authenticated() или отклонить запрос

Использование Iron для защиты данных

Библиотека @hapi/iron используется для упаковки и защиты данных (sealing/unsealing). Это особенно полезно при работе с токенами, cookie-сессиями или подписанными структурами.

Основные операции:

  • Iron.seal(data, password, options)
  • Iron.unseal(sealed, password, options)

Пример использования внутри схемы:

const Iron = require('@hapi/iron');

server.auth.scheme('iron-scheme', (server, options) => {

    return {
        authenticate: async (request, h) => {
            const cookie = request.state.session;

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

            let credentials;

            try {
                credentials = await Iron.unseal(
                    cookie,
                    options.password,
                    Iron.defaults
                );
            }
            catch (err) {
                throw Boom.unauthorized('Invalid session');
            }

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

Связь схемы и стратегии

После регистрации схемы она не используется напрямую. Необходимо создать стратегию:

server.auth.strategy('session', 'iron-scheme', {
    password: 'super-secure-password'
});
  • 'session' — имя стратегии
  • 'iron-scheme' — зарегистрированная схема
  • объект — параметры, передаваемые в фабрику схемы

Далее стратегия может быть применена к маршрутам:

server.route({
    method: 'GET',
    path: '/profile',
    options: {
        auth: 'session'
    },
    handler: (request, h) => {
        return request.auth.credentials;
    }
});

Поток выполнения аутентификации

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

  1. Hapi определяет стратегию маршрута
  2. Находит связанную схему
  3. Вызывает authenticate
  4. Схема извлекает данные запроса
  5. При необходимости выполняется Iron.unseal
  6. Возвращается объект credentials
  7. Запрос продолжает выполнение с доступными request.auth

Обработка ошибок внутри схемы

Ошибка аутентификации должна возвращаться через исключение:

throw Boom.unauthorized('Reason');

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

return h.unauthenticated(Boom.unauthorized('Invalid token'));

Важно различать:

  • отсутствие данных (unauthenticated)
  • некорректные данные (unauthorized)
  • внутренние ошибки (internal server error)

Конфигурация Iron в схеме

Iron требует согласованной конфигурации:

const options = {
    password: 'key',
    integrity: {
        salt: 'random-salt',
        iterations: 1000
    },
    ttl: 24 * 60 * 60 * 1000
};

Эти параметры влияют на:

  • стойкость к подделке данных
  • срок действия токена
  • совместимость unseal/seal операций

Пример полноценной схемы на основе Iron

const Iron = require('@hapi/iron');
const Boom = require('@hapi/boom');

server.auth.scheme('iron-session', (server, options) => {

    return {
        authenticate: async (request, h) => {

            const token = request.state.auth;

            if (!token) {
                throw Boom.unauthorized('No auth token');
            }

            let session;

            try {
                session = await Iron.unseal(
                    token,
                    options.password,
                    Iron.defaults
                );
            }
            catch (err) {
                throw Boom.unauthorized('Token invalid or expired');
            }

            return h.authenticated({
                credentials: session,
                artifacts: token
            });
        }
    };
});

Расширение схемы: refresh логика

Схема может включать дополнительную логику обновления токена:

if (session.exp < Date.now()) {
    throw Boom.unauthorized('Session expired');
}

или автоматическую переупаковку:

const renewed = await Iron.seal(session, options.password, Iron.defaults);

Использование artifacts

artifacts позволяют сохранить исходные данные запроса аутентификации:

return h.authenticated({
    credentials: session,
    artifacts: token
});

Это полезно для:

  • логирования
  • повторной валидации
  • отладки сессий

Разделение ответственности схемы

Схема должна оставаться изолированной от:

  • маршрутов
  • бизнес-логики приложения
  • хранения данных

Её задача ограничена:

  • извлечением данных
  • проверкой
  • декодированием (включая Iron)
  • возвратом результата

Типичные ошибки при реализации

Отсутствие обработки исключений Iron

Любой unseal без try/catch приводит к падению запроса.

Хранение логики приложения в схеме

Схема не должна обращаться к базе данных без необходимости.

Неправильное использование options

Параметры схемы должны быть неизменяемыми во время выполнения.


Поведение при множественных стратегиях

Hapi позволяет регистрировать несколько стратегий на основе одной схемы:

server.auth.strategy('sessionA', 'iron-session', { password: 'A' });
server.auth.strategy('sessionB', 'iron-session', { password: 'B' });

Каждая стратегия использует один и тот же алгоритм, но разные ключи или параметры.


Итоговая модель работы схемы

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