Сброс пароля в системах аутентификации почти всегда опирается на комбинацию двух механизмов: одноразовые временные токены и хеширование новых паролей через bcrypt.js. Разделение этих задач принципиально: токен отвечает за временное подтверждение действия, bcrypt.js — за безопасное хранение итогового пароля.
Типичный процесс строится вокруг нескольких последовательных шагов:
Ключевой принцип — ни один из этапов не должен раскрывать текущий пароль или позволять его восстановление из базы данных.
Библиотека bcrypt.js применяется исключительно для хранения нового пароля. Она не участвует в генерации или проверке временных токенов.
Особенности bcrypt.js:
Пример базового использования:
import bcrypt fr om 'bcryptjs';
const saltRounds = 12;
async function hashPassword(password) {
const hash = await bcrypt.hash(password, saltRounds);
return hash;
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
В контексте сброса пароля bcrypt.js применяется на финальном этапе — при сохранении нового значения пароля.
Токен сброса пароля должен обладать несколькими свойствами:
Чаще всего используется модуль crypto:
import crypto fr om 'crypto';
function generateResetToken() {
return crypto.randomBytes(32).toString('hex');
}
Полученный токен не хранится в открытом виде в базе данных. Это важный момент безопасности.
Если токен хранится в базе данных в исходном виде, компрометация базы приводит к возможности мгновенного использования всех активных ссылок сброса.
Поэтому применяется хеширование токена перед сохранением. В отличие от паролей, bcrypt.js здесь используется редко, поскольку:
Чаще применяется SHA-256:
import crypto from 'crypto';
function hashToken(token) {
return crypto.createHash('sha256').update(token).digest('hex');
}
В базе данных хранится только хеш:
{
userId: "123",
resetTokenHash: "...",
resetTokenExpires: Date.now() + 1000 * 60 * 15
}
const token = generateResetToken();
const tokenHash = hashToken(token);
await db.users.update({
wh ere: { email },
data: {
resetTokenHash: tokenHash,
resetTokenExpires: Date.now() + 15 * 60 * 1000
}
});
Формируется URL:
https://example.com/reset-password?token=TOKEN&email=user@mail.com
Отправляется именно исходный токен, а не его хеш.
При получении запроса на сброс выполняется сопоставление:
const receivedHash = hashToken(tokenFromRequest);
const user = await db.users.findUnique({
wh ere: { email }
});
const isValid =
user.resetTokenHash === receivedHash &&
user.resetTokenExpires > Date.now();
Если условия не выполняются — токен считается недействительным.
После успешной проверки токена выполняется хеширование нового пароля:
import bcrypt fr om 'bcryptjs';
const saltRounds = 12;
async function setNewPassword(userId, newPassword) {
const passwordHash = await bcrypt.hash(newPassword, saltRounds);
await db.users.update({
wh ere: { id: userId },
data: {
passwordHash,
resetTokenHash: null,
resetTokenExpires: null
}
});
}
Сброс токена после использования является обязательным шагом, предотвращающим повторное применение ссылки.
Хотя технически возможно хешировать токен через bcrypt.js, на практике это создаёт лишнюю нагрузку и не даёт преимуществ:
Поэтому оптимальная схема:
15–30 минут является распространённым диапазоном. Более длительное время увеличивает риск компрометации.
После успешного сброса:
Запросы на генерацию токенов должны ограничиваться:
Это снижает вероятность перебора.
Токен передаётся в query string, что требует:
При сравнении хешей важно избегать утечек через timing attacks. Для этого используется:
import crypto from 'crypto';
crypto.timingSafeEqual(
Buffer.from(hashA),
Buffer.from(hashB)
);
Сброс пароля всегда является частью более широкой системы:
Такая структура позволяет изолировать ответственность компонентов:
Создаёт прямую угрозу компрометации аккаунтов при утечке базы.
Токен без TTL фактически становится постоянным ключом доступа.
Необнуляемые токены позволяют многократный сброс пароля.
Увеличивает нагрузку и снижает отзывчивость системы без добавления безопасности.
Практическая модель в базе данных:
{
id: "123",
email: "user@mail.com",
passwordHash: "...bcrypt...",
resetTokenHash: null,
resetTokenExpires: null
}
Разделение полей позволяет чётко отделить постоянные и временные данные.
Такие меры уменьшают поверхность атаки даже при частичной компрометации системы.
Если генерируется новый токен до истечения старого:
Это предотвращает накопление активных ссылок восстановления.
bcrypt.js выступает финальным барьером защиты. Даже при наличии доступа к базе данных:
Сочетание временных токенов и bcrypt.js формирует двухуровневую систему: