В экосистеме Node.js и JavaScript две библиотеки часто участвуют в построении системы аутентификации:
password-hash — предназначена для хеширования и
проверки паролей;jsonwebtoken — используется для создания и проверки
JWT-токенов.Несмотря на то, что обе библиотеки связаны с безопасностью и аутентификацией, они решают принципиально разные задачи.
| Библиотека | Назначение |
|---|---|
password-hash |
Защита паролей |
jsonwebtoken |
Идентификация пользователя через токены |
password-hashБиблиотека password-hash выполняет:
Пример:
const passwordHash = require('password-hash');
const hashedPassword = passwordHash.generate('myPassword');
console.log(hashedPassword);
Результат:
sha1$3$e2f0a1b4$8d8a9c6...
Неправильный подход:
{
login: 'admin',
password: '123456'
}
При утечке базы данных злоумышленник мгновенно получает все пароли.
Правильный подход:
{
login: 'admin',
password: 'sha1$3$e2f0a1b4$8d8a9c6...'
}
Теперь даже владелец базы не знает исходный пароль пользователя.
jsonwebtokenjsonwebtoken реализует механизм JWT (JSON Web
Token).
JWT — это подписанный токен, содержащий информацию о пользователе.
Пример создания токена:
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{
id: 15,
role: 'admin'
},
'secret-key',
{
expiresIn: '1h'
}
);
console.log(token);
Результат:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT нужен не для хранения пароля, а для подтверждения личности пользователя после входа.
Полный процесс аутентификации обычно выглядит так:
Пользователь вводит логин и пароль.
Сервер проверяет пароль через
password-hash.
Если пароль корректен:
jsonwebtoken;Клиент использует токен при дальнейших запросах.
password-hash в системе авторизацииВо время регистрации пароль хешируется:
const passwordHash = require('password-hash');
const userPassword = 'qwerty123';
const hash = passwordHash.generate(userPassword);
console.log(hash);
В базе сохраняется только хеш:
{
email: 'user@mail.com',
password: 'sha1$3$d1f9c2$8d7f1...'
}
При авторизации:
const passwordHash = require('password-hash');
const enteredPassword = 'qwerty123';
const savedHash = user.password;
const isValid = passwordHash.verify(
enteredPassword,
savedHash
);
console.log(isValid);
Если пароль совпадает:
true
jsonwebtoken в системе авторизацииПосле успешной проверки пароля создаётся токен.
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{
userId: 7
},
'super-secret',
{
expiresIn: '24h'
}
);
const jwt = require('jsonwebtoken');
try {
const decoded = jwt.verify(
token,
'super-secret'
);
console.log(decoded);
} catch (err) {
console.log('Invalid token');
}
password-hash не заменяет JWTИногда возникает ошибочное представление, что после проверки пароля пользователь уже считается аутентифицированным.
Но password-hash:
После каждого HTTP-запроса сервер забудет пользователя.
JWT решает именно эту проблему.
password-hashJWT не предназначен для хранения паролей.
Опасный пример:
jwt.sign({
password: '123456'
}, secret);
Даже подписанный JWT можно декодировать.
JWT:
Пароль внутри токена — критическая ошибка безопасности.
password-hash — защита
данныхБиблиотека работает с:
Главная цель:
jsonwebtoken
— транспорт аутентификацииJWT решает задачи:
JWT особенно полезен в REST API.
Сервер не хранит сессии.
Каждый запрос содержит:
Authorization: Bearer TOKEN
Сервер проверяет подпись токена и получает данные пользователя.
Классическая схема:
Недостатки:
JWT позволяет отказаться от хранения сессий.
Токен содержит:
Преимущества:
password-hash считается устаревшимБиблиотека долгое время использовала SHA1.
Современные рекомендации безопасности требуют:
SHA1 считается криптографически слабым.
Самое распространённое решение.
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 10);
Проверка:
const valid = await bcrypt.compare(
password,
hash
);
Современный алгоритм победителя Password Hashing Competition.
const argon2 = require('argon2');
const hash = await argon2.hash(password);
| Алгоритм | Безопасность | Скорость | Рекомендация |
|---|---|---|---|
| SHA1 | Низкая | Очень высокая | Не рекомендуется |
| bcrypt | Высокая | Средняя | Хорошо |
| scrypt | Высокая | Средняя | Хорошо |
| argon2 | Очень высокая | Настраиваемая | Лучший вариант |
На практике чаще используется:
bcrypt/argon2 + jsonwebtoken
Схема:
Пароль
↓
bcrypt
↓
Проверка
↓
jsonwebtoken
↓
JWT
const bcrypt = require('bcrypt');
async function register(password) {
const hash = await bcrypt.hash(password, 12);
return {
password: hash
};
}
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
async function login(password, user) {
const valid = await bcrypt.compare(
password,
user.password
);
if (!valid) {
throw new Error('Invalid password');
}
const token = jwt.sign(
{
id: user.id
},
'secret-key',
{
expiresIn: '1h'
}
);
return token;
}
JWT состоит из трёх частей:
HEADER.PAYLOAD.SIGNATURE
{
"alg": "HS256",
"typ": "JWT"
}
{
"id": 1,
"role": "admin"
}
Создаётся через секретный ключ.
Токен можно декодировать:
const decoded = jwt.decode(token);
console.log(decoded);
Поэтому в JWT нельзя хранить:
JWT почти всегда имеет ограничение по времени:
jwt.sign(data, secret, {
expiresIn: '15m'
});
После истечения срока:
jwt.verify(token, secret);
выдаст ошибку.
Распространённая схема:
| Токен | Срок |
|---|---|
| Access Token | 15 минут |
| Refresh Token | 30 дней |
Access Token:
Refresh Token:
Плохо:
jwt.sign({
password: '123456'
}, secret);
Опасно:
const secret = '123';
Нужно:
const secret =
'8f7c6e2d1b9a4f5c7e8a1d2c';
Неправильно:
jwt.sign(data, secret);
Токен станет бессрочным.
password-hash основан на устаревших подходах.
Для современных приложений предпочтительнее:
password-hashХеширование должно быть медленным.
Причина:
Подпись и проверка должны быть быстрыми.
JWT участвует почти в каждом запросе API.
| Возможность | password-hash | jsonwebtoken |
|---|---|---|
| Хранение паролей | Да | Нет |
| Проверка паролей | Да | Нет |
| Создание токенов | Нет | Да |
| Авторизация API | Нет | Да |
| Stateless-аутентификация | Нет | Да |
| Хранение ролей | Нет | Да |
| Работа с сессиями | Нет | Частично |
Наиболее типичная схема:
Регистрация:
Пароль → bcrypt → База
Логин:
Пароль → bcrypt.compare()
Успех:
jsonwebtoken.sign()
API:
jsonwebtoken.verify()
Middleware Express:
const jwt = require('jsonwebtoken');
function auth(req, res, next) {
const authHeader =
req.headers.authorization;
if (!authHeader) {
return res.sendStatus(401);
}
const token =
authHeader.split(' ')[1];
try {
const user = jwt.verify(
token,
'secret-key'
);
req.user = user;
next();
} catch {
return res.sendStatus(403);
}
}
Современная безопасная схема:
argon2/bcrypt
+
jsonwebtoken
+
HTTPS
+
refresh tokens
+
httpOnly cookies
JWT без HTTPS уязвим для перехвата.
Если токен украден:
Плюсы:
Минусы:
Плюсы:
Минусы:
JWT удобно хранит права доступа:
jwt.sign({
id: 5,
role: 'admin'
}, secret);
Middleware:
if (req.user.role !== 'admin') {
return res.sendStatus(403);
}
password-hash отвечает на вопрос:
Правильный ли пароль?
jsonwebtoken отвечает на вопрос:
Кто выполняет запрос?
Это два разных уровня безопасности:
| Уровень | Инструмент |
|---|---|
| Проверка личности | password-hash |
| Подтверждение авторизации | JWT |
Для новых проектов типичная архитектура строится следующим образом:
bcrypt или
argon2;password-hash исторически решал задачу безопасного
хранения паролей, однако современные системы чаще переходят на
bcrypt и argon2. jsonwebtoken
остаётся одним из основных инструментов stateless-аутентификации в
Node.js и JavaScript-приложениях.