Использование в Fastify

Работа с паролями в серверных приложениях на Node.js требует строгого соблюдения принципов безопасности: хранение только хешей, использование соли, контроль алгоритма и защита от перебора. Библиотека password-hash предоставляет простой интерфейс для создания и проверки хешей, а Fastify позволяет встроить этот процесс в высокопроизводительный HTTP-сервер.

Установка необходимых пакетов:

npm install fastify password-hash

Fastify используется как основа API, а password-hash — как слой обработки паролей.


Принцип работы password-hash

Библиотека реализует классическую схему:

  • генерация соли (salt)
  • хеширование пароля с солью
  • хранение результата в одном строковом формате
  • проверка пароля через повторное вычисление хеша

Пример использования без Fastify:

const passwordHash = require('password-hash');

const hashedPassword = passwordHash.generate('mySecurePassword');

const isValid = passwordHash.verify('mySecurePassword', hashedPassword);

Строка хеша содержит алгоритм, соль и сам хеш, что упрощает хранение в базе данных.


Интеграция с Fastify через сервисный слой

В архитектуре Fastify логично вынести работу с паролями в отдельный модуль, чтобы не смешивать бизнес-логику и обработчики маршрутов.

Сервис хеширования

// services/passwordService.js
const passwordHash = require('password-hash');

function hashPassword(password) {
  return passwordHash.generate(password);
}

function verifyPassword(password, hashed) {
  return passwordHash.verify(password, hashed);
}

module.exports = {
  hashPassword,
  verifyPassword
};

Такой слой упрощает тестирование и дальнейшую замену алгоритма.


Регистрация пользователя в Fastify

При создании пользователя пароль никогда не сохраняется в открытом виде.

Пример маршрута регистрации

const fastify = require('fastify')({ logger: true });
const { hashPassword } = require('./services/passwordService');

fastify.post('/register', async (request, reply) => {
  const { email, password } = request.body;

  const hashed = hashPassword(password);

  const user = {
    email,
    password: hashed
  };

  // здесь обычно идет запись в БД
  return reply.code(201).send(user);
});

Ключевой момент: в базу данных попадает только результат хеширования.


Авторизация и проверка пароля

При логине выполняется сравнение введённого пароля с сохранённым хешем.

const { verifyPassword } = require('./services/passwordService');

fastify.post('/login', async (request, reply) => {
  const { email, password } = request.body;

  // имитация поиска пользователя в базе
  const userFromDb = {
    email,
    password: '$hashed_password_from_db'
  };

  const isValid = verifyPassword(password, userFromDb.password);

  if (!isValid) {
    return reply.code(401).send({ error: 'Invalid credentials' });
  }

  return reply.send({ status: 'ok' });
});

Проверка выполняется без раскрытия исходного пароля.


Использование hooks Fastify для централизованной логики

Fastify позволяет внедрять хуки, которые полезны при построении систем аутентификации.

Пример preHandler для защиты маршрутов

fastify.addHook('preHandler', async (request, reply) => {
  if (!request.headers.authorization) {
    return reply.code(401).send({ error: 'No token' });
  }
});

Хотя password-hash не работает с токенами напрямую, он часто используется в связке с JWT или сессионной моделью.


Хранение паролей в базе данных

Хеш, создаваемый password-hash, уже содержит всю необходимую информацию:

  • алгоритм
  • соль
  • итоговый хеш

Поэтому структура таблицы пользователей минимальна:

CRE ATE   TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  password TEXT NOT NULL
);

Поле password хранит только результат passwordHash.generate().


Обработка ошибок и некорректных данных

При интеграции важно учитывать:

  • пустые строки пароля
  • слишком короткие значения
  • отсутствие поля password в запросе

Пример валидации Fastify:

fastify.post('/register', {
  schema: {
    body: {
      type: 'object',
      required: ['email', 'password'],
      properties: {
        email: { type: 'string' },
        password: { type: 'string', minLength: 6 }
      }
    }
  }
}, async (request, reply) => {
  const { email, password } = request.body;

  const hashed = hashPassword(password);

  return { email, password: hashed };
});

Встроенная схема снижает риск попадания некорректных данных в слой хеширования.


Производительность при высокой нагрузке

Fastify оптимизирован под высокую пропускную способность, однако password-hash выполняет синхронное хеширование, что может стать узким местом при большом количестве запросов.

Типичные меры:

  • ограничение частоты запросов (rate limiting)
  • вынос хеширования в отдельный слой
  • использование worker threads при необходимости масштабирования

Пример ограничения:

fastify.register(require('@fastify/rate-limit'), {
  max: 100,
  timeWindow: '1 minute'
});

Совместная работа с JWT аутентификацией

На практике password-hash часто используется только на этапе логина и регистрации, а дальше применяется JWT.

const jwt = require('jsonwebtoken');

function createToken(user) {
  return jwt.sign({ id: user.id }, 'secretKey');
}

После проверки пароля генерируется токен, а password-hash больше не участвует в запросах.


Типичные ошибки при использовании

Часто встречающиеся проблемы:

  • хранение открытого пароля вместе с хешем
  • повторное хеширование уже хешированного значения
  • отсутствие проверки verifyPassword при логине
  • использование одной и той же соли вручную (что не требуется библиотекой)

Особенно критична ошибка двойного хеширования:

const wrong = passwordHash.generate(passwordHash.generate('test'));

Это приводит к невозможности проверки пароля.


Организация кода в масштабируемом приложении Fastify

Рекомендуемая структура:

/plugins
/services
/routes
/controllers

Password-hash обычно находится в /services/authService.js, а маршруты лишь вызывают методы:

// controllers/authController.js
const { hashPassword } = require('../services/passwordService');

Такой подход снижает связанность компонентов и упрощает тестирование логики безопасности.