В серверных приложениях на Node.js хеширование паролей нельзя рассматривать как второстепенную задачу. Любая ошибка в реализации приводит к критическим уязвимостям. В экосистеме JavaScript наиболее распространённым инструментом для этого остаётся bcrypt.js — чистая реализация алгоритма bcrypt без нативных зависимостей.
Fastify предоставляет удобную архитектуру плагинов и хуков, которая позволяет централизованно интегрировать bcrypt и контролировать процессы хеширования и проверки паролей на уровне фреймворка.
bcrypt.js реализует адаптивную функцию хеширования паролей, основанную на Blowfish-шифровании. Основная идея заключается в том, что вычислительная стоимость хеширования может быть увеличена с течением времени.
Ключевые особенности:
Простейший пример использования:
import bcrypt from 'bcryptjs';
const password = 'secret123';
const hash = await bcrypt.hash(password, 10);
const isValid = await bcrypt.compare('secret123', hash);
Fastify строится вокруг плагинов, что делает его идеальным для инкапсуляции логики безопасности.
Создание плагина для bcrypt позволяет:
decoratorsimport 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:
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 hooks позволяют внедрять bcrypt на уровне жизненного цикла запроса. Это особенно полезно при создании пользователей.
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 для предварительной
обработки данных до валидации схемой:
fastify.addHook('preValidation', async (request) => {
if (request.body?.password && request.body.password.length < 6) {
throw new Error('Password too weak');
}
});
В сочетании с bcrypt это формирует двухуровневую защиту:
bcrypt — CPU-интенсивная операция. Даже в async-версии она блокирует event loop при высокой нагрузке.
В Fastify важно учитывать:
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;
}
});
bcrypt напрямую не участвует в проверке токенов, но может использоваться для дополнительных проверок безопасности.
fastify.addHook('onRequest', async (request, reply) => {
const token = request.headers.authorization;
if (!token) {
return reply.code(401).send();
}
});
В связке с bcrypt это может применяться для:
При проектировании системы важно учитывать следующие моменты:
В системах с высокой нагрузкой можно кэшировать результат
compare, если пароль не менялся.
Каждый сервис должен иметь свой bcrypt-инстанс, если требуется различная политика безопасности.
На практике часто встречаются следующие проблемы:
Корректная модель интеграции выглядит так:
bcrypt должен находиться строго между сервисным и инфраструктурным слоями, не проникая в бизнес-логику напрямую.
Типичная схема:
const user = await fastify.auth.login(email, password);
if (user) {
const token = fastify.jwt.sign({ id: user.id });
return { token };
}
bcrypt в этой архитектуре выполняет только одну функцию — подтверждение личности через пароль, не вмешиваясь в управление сессией.