password-hash против jsonwebtoken для аутентификации

В экосистеме 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...'
}

Теперь даже владелец базы не знает исходный пароль пользователя.


Что такое jsonwebtoken

jsonwebtoken реализует механизм 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 нужен не для хранения пароля, а для подтверждения личности пользователя после входа.


Принцип работы аутентификации

Полный процесс аутентификации обычно выглядит так:

  1. Пользователь вводит логин и пароль.

  2. Сервер проверяет пароль через password-hash.

  3. Если пароль корректен:

    • создаётся JWT через jsonwebtoken;
    • токен отправляется клиенту.
  4. Клиент использует токен при дальнейших запросах.


Роль 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 в системе авторизации

После успешной проверки пароля создаётся токен.

Генерация JWT

const jwt = require('jsonwebtoken');

const token = jwt.sign(
  {
    userId: 7
  },
  'super-secret',
  {
    expiresIn: '24h'
  }
);

Проверка JWT

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 решает именно эту проблему.


Почему JWT не заменяет password-hash

JWT не предназначен для хранения паролей.

Опасный пример:

jwt.sign({
  password: '123456'
}, secret);

Даже подписанный JWT можно декодировать.

JWT:

  • кодируется;
  • подписывается;
  • но не шифруется по умолчанию.

Пароль внутри токена — критическая ошибка безопасности.


Архитектурное различие

password-hash — защита данных

Библиотека работает с:

  • криптографией;
  • необратимыми преобразованиями;
  • безопасным хранением паролей.

Главная цель:

  • сделать восстановление пароля невозможным.

jsonwebtoken — транспорт аутентификации

JWT решает задачи:

  • передачи идентификатора пользователя;
  • подтверждения подлинности;
  • хранения ролей и прав;
  • поддержки stateless-авторизации.

Stateless-аутентификация

JWT особенно полезен в REST API.

Сервер не хранит сессии.

Каждый запрос содержит:

Authorization: Bearer TOKEN

Сервер проверяет подпись токена и получает данные пользователя.


Stateful и Stateless

Stateful

Классическая схема:

  1. Сервер создаёт сессию.
  2. Сессия хранится в памяти или Redis.
  3. Клиент получает session id.

Недостатки:

  • сложное масштабирование;
  • хранение состояния;
  • необходимость синхронизации.

Stateless

JWT позволяет отказаться от хранения сессий.

Токен содержит:

  • идентификатор пользователя;
  • срок жизни;
  • дополнительные данные.

Преимущества:

  • горизонтальное масштабирование;
  • удобство для микросервисов;
  • простота REST API.

Почему password-hash считается устаревшим

Библиотека долгое время использовала SHA1.

Современные рекомендации безопасности требуют:

  • bcrypt;
  • scrypt;
  • argon2.

SHA1 считается криптографически слабым.


Современные альтернативы

bcrypt

Самое распространённое решение.

const bcrypt = require('bcrypt');

const hash = await bcrypt.hash(password, 10);

Проверка:

const valid = await bcrypt.compare(
  password,
  hash
);

argon2

Современный алгоритм победителя Password Hashing Competition.

const argon2 = require('argon2');

const hash = await argon2.hash(password);

Сравнение алгоритмов

Алгоритм Безопасность Скорость Рекомендация
SHA1 Низкая Очень высокая Не рекомендуется
bcrypt Высокая Средняя Хорошо
scrypt Высокая Средняя Хорошо
argon2 Очень высокая Настраиваемая Лучший вариант

Типичная связка в современном Node.js

На практике чаще используется:

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

JWT состоит из трёх частей:

HEADER.PAYLOAD.SIGNATURE

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

{
  "id": 1,
  "role": "admin"
}

Signature

Создаётся через секретный ключ.


Почему JWT нельзя считать шифрованием

Токен можно декодировать:

const decoded = jwt.decode(token);

console.log(decoded);

Поэтому в JWT нельзя хранить:

  • пароли;
  • номера карт;
  • приватные ключи;
  • персональные данные.

Срок жизни токена

JWT почти всегда имеет ограничение по времени:

jwt.sign(data, secret, {
  expiresIn: '15m'
});

После истечения срока:

jwt.verify(token, secret);

выдаст ошибку.


Refresh Token

Распространённая схема:

Токен Срок
Access Token 15 минут
Refresh Token 30 дней

Access Token:

  • используется для API.

Refresh Token:

  • получает новый access token.

Уязвимости при неправильном использовании

Ошибка №1 — хранение пароля в JWT

Плохо:

jwt.sign({
  password: '123456'
}, secret);

Ошибка №2 — слабый secret

Опасно:

const secret = '123';

Нужно:

const secret =
'8f7c6e2d1b9a4f5c7e8a1d2c';

Ошибка №3 — отсутствие срока жизни

Неправильно:

jwt.sign(data, secret);

Токен станет бессрочным.


Ошибка №4 — использование SHA1 для новых проектов

password-hash основан на устаревших подходах.

Для современных приложений предпочтительнее:

  • argon2;
  • bcrypt.

Производительность

password-hash

Хеширование должно быть медленным.

Причина:

  • защита от brute-force.

JWT

Подпись и проверка должны быть быстрыми.

JWT участвует почти в каждом запросе API.


Сравнение по задачам

Возможность password-hash jsonwebtoken
Хранение паролей Да Нет
Проверка паролей Да Нет
Создание токенов Нет Да
Авторизация API Нет Да
Stateless-аутентификация Нет Да
Хранение ролей Нет Да
Работа с сессиями Нет Частично

Когда используются вместе

Наиболее типичная схема:

Регистрация:
Пароль → bcrypt → База

Логин:
Пароль → bcrypt.compare()

Успех:
jsonwebtoken.sign()

API:
jsonwebtoken.verify()

Механизм проверки пользователя в API

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

Почему HTTPS обязателен

JWT без HTTPS уязвим для перехвата.

Если токен украден:

  • злоумышленник получает доступ к аккаунту.

Cookie или LocalStorage

LocalStorage

Плюсы:

  • простота.

Минусы:

  • XSS-уязвимости.

Плюсы:

  • JavaScript не может прочитать cookie;
  • выше защита от XSS.

Минусы:

  • требуется CSRF-защита.

JWT и роли пользователей

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;
  • после успешного входа создаётся JWT;
  • токены имеют срок жизни;
  • refresh token хранится отдельно;
  • API использует middleware проверки токена;
  • соединение работает только через HTTPS.

password-hash исторически решал задачу безопасного хранения паролей, однако современные системы чаще переходят на bcrypt и argon2. jsonwebtoken остаётся одним из основных инструментов stateless-аутентификации в Node.js и JavaScript-приложениях.