Fastify: хуки и плагины для работы с bcrypt

В серверных приложениях на Node.js хеширование паролей нельзя рассматривать как второстепенную задачу. Любая ошибка в реализации приводит к критическим уязвимостям. В экосистеме JavaScript наиболее распространённым инструментом для этого остаётся bcrypt.js — чистая реализация алгоритма bcrypt без нативных зависимостей.

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


Основы bcrypt.js в контексте серверной разработки

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

Ключевые особенности:

  • адаптивная сложность (cost factor)
  • невозможность восстановления исходного пароля
  • встроенная соль (salt)
  • устойчивость к радужным таблицам

Простейший пример использования:

import bcrypt from 'bcryptjs';

const password = 'secret123';

const hash = await bcrypt.hash(password, 10);
const isValid = await bcrypt.compare('secret123', hash);

Интеграция bcrypt.js через Fastify-плагин

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

Создание плагина для bcrypt позволяет:

  • централизовать настройки cost factor
  • избежать дублирования кода
  • внедрять методы через decorators
  • контролировать производительность

Базовый плагин bcrypt

import fp from 'fastify-plugin';
import bcrypt from 'bcryptjs';

async function bcryptPlugin(fastify, options) {
  const saltRounds = options.saltRounds || 10;

  fastify.decorate('bcrypt', {
    hash: async (password) => {
      return bcrypt.hash(password, saltRounds);
    },

    compare: async (password, hash) => {
      return bcrypt.compare(password, hash);
    }
  });
}

export default fp(bcryptPlugin);

Подключение:

import Fastify from 'fastify';
import bcryptPlugin from './plugins/bcrypt.js';

const fastify = Fastify();

fastify.register(bcryptPlugin, {
  saltRounds: 12
});

Использование bcrypt через декораторы Fastify

После регистрации плагина bcrypt становится доступен через экземпляр Fastify:

fastify.post('/register', async (request, reply) => {
  const { password } = request.body;

  const hash = await fastify.bcrypt.hash(password);

  // сохранение в БД
  return { success: true };
});
fastify.post('/login', async (request, reply) => {
  const { password, hashFromDb } = request.body;

  const valid = await fastify.bcrypt.compare(password, hashFromDb);

  if (!valid) {
    return reply.code(401).send({ error: 'Invalid credentials' });
  }

  return { success: true };
});

Использование хуков Fastify для автоматического хеширования

Fastify hooks позволяют внедрять bcrypt на уровне жизненного цикла запроса. Это особенно полезно при создании пользователей.

preHandler для хеширования пароля

fastify.addHook('preHandler', async (request, reply) => {
  if (request.routerPath === '/register' && request.body?.password) {
    const hash = await fastify.bcrypt.hash(request.body.password);
    request.body.password = hash;
  }
});

Такой подход позволяет:

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

Валидация и защита через preValidation hook

Можно использовать preValidation для предварительной обработки данных до валидации схемой:

fastify.addHook('preValidation', async (request) => {
  if (request.body?.password && request.body.password.length < 6) {
    throw new Error('Password too weak');
  }
});

В сочетании с bcrypt это формирует двухуровневую защиту:

  1. проверка качества пароля
  2. его хеширование

Асинхронная природа bcrypt и влияние на Fastify

bcrypt — CPU-интенсивная операция. Даже в async-версии она блокирует event loop при высокой нагрузке.

В Fastify важно учитывать:

  • рост latency при высоких saltRounds
  • необходимость балансировки нагрузки
  • возможность выноса в worker_threads

Пример использования worker_threads

import { Worker } from 'worker_threads';

function hashPassword(password) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./bcrypt-worker.js', {
      workerData: password
    });

    worker.on('message', resolve);
    worker.on('error', reject);
  });
}

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

Fastify-плагин можно расширить до полноценного слоя аутентификации.

fastify.decorate('auth', {
  register: async (user) => {
    const hash = await fastify.bcrypt.hash(user.password);

    return fastify.db.users.insert({
      email: user.email,
      password: hash
    });
  },

  login: async (email, password) => {
    const user = await fastify.db.users.findByEmail(email);

    if (!user) return null;

    const valid = await fastify.bcrypt.compare(password, user.password);

    return valid ? user : null;
  }
});

Использование onRequest hook для защиты маршрутов

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

fastify.addHook('onRequest', async (request, reply) => {
  const token = request.headers.authorization;

  if (!token) {
    return reply.code(401).send();
  }
});

В связке с bcrypt это может применяться для:

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

Оптимизация bcrypt в Fastify приложениях

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

1. Ограничение saltRounds

  • 8–10 — оптимально для большинства приложений
  • 12+ — высокая безопасность, но заметная нагрузка

2. Кэширование результатов сравнения

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

3. Изоляция через плагины

Каждый сервис должен иметь свой bcrypt-инстанс, если требуется различная политика безопасности.


Ошибки интеграции bcrypt в Fastify

На практике часто встречаются следующие проблемы:

  • хеширование в бизнес-логике вместо плагина
  • блокирующие вызовы bcrypt.hashSync
  • отсутствие контроля saltRounds
  • повторное хеширование уже захешированного пароля
  • утечка plain-text паролей в логах Fastify

Структурирование архитектуры безопасности

Корректная модель интеграции выглядит так:

  • Fastify plugin (bcrypt abstraction layer)
  • hooks (preHandler / preValidation)
  • service layer (auth logic)
  • repository layer (database access)

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


Расширение: комбинирование bcrypt с JWT

Типичная схема:

  1. bcrypt проверяет пароль
  2. Fastify генерирует JWT
  3. клиент использует токен
const user = await fastify.auth.login(email, password);

if (user) {
  const token = fastify.jwt.sign({ id: user.id });
  return { token };
}

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