Хешируй пароли дорогой функцией, осознанно выбирай между сессиями и JWT и защищай маршруты через middleware.
Открыть этот урок в KodokonХранить пароль - значит готовиться к будущей утечке: проектируй хеширование так, чтобы оно пережило кражу базы данных. Быстрый хеш вроде SHA-256 перебирается на GPU со скоростью миллиардов попыток в секунду. Нужна намеренно дорогая функция - scrypt из node:crypto, либо bcrypt/argon2 - и случайная соль для каждого пользователя, которая обесценивает заранее посчитанные таблицы. Соль не секрет: она хранится в открытом виде рядом с хешем.
import {
randomBytes,
scryptSync,
timingSafeEqual,
} from 'node:crypto';
export function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const hash = scryptSync(password, salt, 64)
.toString('hex');
return `${salt}:${hash}`;
}
export function verifyPassword(password, stored) {
const [salt, hash] = stored.split(':');
const candidate = scryptSync(password, salt, 64);
const expected = Buffer.from(hash, 'hex');
return timingSafeEqual(candidate, expected);
}Две модели, чтобы держать пользователя авторизованным. Сессия: непрозрачный идентификатор в cookie с флагом httpOnly, состояние на стороне сервера. Мгновенный отзыв, cookie невидим для JavaScript, но общее хранилище (Redis) становится необходимым, как только появляется второй экземпляр. JWT: подписанное состояние зашито в токен, проверяется без всякого хранилища - идеально между сервисами - но не отзывается до истечения срока. Честный компромисс: сессии для классического веб-приложения, короткоживущие JWT для API, которые потребляют сторонние клиенты, или для распределённых архитектур.
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET;
if (!SECRET) {
throw new Error('JWT_SECRET must be set');
}
export function signToken(user) {
return jwt.sign(
{ sub: user.id, email: user.email },
SECRET,
{ expiresIn: '15m' },
);
}Middleware защиты выносит контроль доступа в одно место: она извлекает токен из заголовка Authorization: Bearer, проверяет его, прикрепляет расшифрованную личность к req.user - или обрывает всё ошибкой 401, направленной в middleware ошибок из предыдущего урока. Применяй её на уровне роутера (router.use(requireAuth)), чтобы защитить сразу целую группу маршрутов, а не по одному.
import jwt from 'jsonwebtoken';
import { HttpError } from './http-error.js';
const SECRET = process.env.JWT_SECRET;
export function requireAuth(req, res, next) {
const header = req.headers.authorization ?? '';
const [scheme, token] = header.split(' ');
if (scheme !== 'Bearer' || !token) {
return next(new HttpError(401, 'Missing token'));
}
try {
req.user = jwt.verify(token, SECRET);
next();
} catch {
next(new HttpError(401, 'Invalid or expired token'));
}
}timingSafeEqual при проверке пароля?