Работа с паролями относится к критически важной части системы
аутентификации, и выбор слоя, в котором выполняется хеширование,
напрямую влияет на безопасность, тестируемость и масштабируемость
приложения. Использование библиотеки bcrypt.js в JavaScript предполагает
не просто технический вызов функции hash, а правильное
распределение ответственности между слоями архитектуры.
Хеширование пароля должно быть строго ограничено инфраструктурным или сервисным уровнем, не затрагивая контроллеры, маршруты или клиентскую часть. Основная причина заключается в необходимости изоляции криптографической логики от бизнес-логики и транспортного слоя.
В доменной модели системы пароль существует как сущность только до момента его обработки. После этого он перестаёт существовать в исходном виде и заменяется хешем.
Доменные правила:
Инфраструктурные правила:
Такое разделение позволяет сохранять доменный слой независимым от криптографических деталей.
Контроллеры должны заниматься исключительно обработкой HTTP-запросов и маршрутизацией данных. Размещение логики хеширования в контроллере приводит к нескольким проблемам:
Нарушение SRP (Single Responsibility Principle) Контроллер начинает выполнять криптографические операции, что выходит за рамки его ответственности.
Сложность тестирования Мокирование bcrypt внутри контроллера усложняет unit-тесты.
Повторяемость кода При наличии нескольких точек регистрации логика хеширования дублируется.
Утечка инфраструктурных деталей Контроллер начинает зависеть от конкретной реализации криптографии.
Оптимальное место для работы с bcrypt.js — сервисный слой (AuthService, UserService).
Пример структуры:
/controllers
/services
/repositories
/utils
Сервис отвечает за бизнес-логику, включая:
import bcrypt from 'bcryptjs';
const SALT_ROUNDS = 12;
export class AuthService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async register(email, password) {
const existingUser = await this.userRepository.findByEmail(email);
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);
const user = await this.userRepository.create({
email,
password: hashedPassword,
});
return user;
}
async validateUser(email, password) {
const user = await this.userRepository.findByEmail(email);
if (!user) return null;
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) return null;
return user;
}
}
Репозиторий отвечает за доступ к данным, а не за их трансформацию. Если поместить bcrypt в слой хранения данных, возникает ряд архитектурных проблем:
Репозиторий должен принимать уже готовые данные:
create(user)
update(user)
findByEmail(email)
Иногда хеширование пытаются разместить в middleware, особенно в Express-подобных фреймворках. Это приводит к скрытой логике:
Middleware допустим только для:
Использование bcrypt.js на клиенте считается небезопасным и архитектурно неверным решением.
Причины:
Пароль должен передаваться по защищённому каналу (HTTPS) в сыром виде и хешироваться только на сервере.
Параметр salt rounds определяет вычислительную стоимость хеширования.
Рекомендуемая практика:
config/security.js
export const SECURITY_CONFIG = {
bcryptSaltRounds: 12,
};
Сервис использует конфигурацию:
bcrypt.hash(password, SECURITY_CONFIG.bcryptSaltRounds);
Преимущества:
Сравнение выполняется строго в сервисном слое:
bcrypt.compare(plainPassword, hashedPassword);
Недопустимые места:
bcrypt является CPU-intensive операцией. Использование
bcrypt.js в Node.js требует асинхронного подхода.
Последствия игнорирования:
Правильный подход:
await bcrypt.hashРаспространённые антишаблоны:
bcrypt.compareТипичный жизненный цикл пароля:
Использование bcrypt.js должно быть скрыто за абстракцией сервиса. Это позволяет:
В терминах Clean Architecture:
bcrypt относится к внешнему слою и не должен проникать в ядро системы.