Использование в Express.js: регистрация и логин

Модель хранения паролей и роль хэширования

Хранение паролей в исходном виде недопустимо даже в небольших учебных проектах. Любая утечка базы данных в таком случае приводит к компрометации всех пользователей. Хэширование решает эту проблему за счёт необратимого преобразования пароля в строку фиксированной длины.

Библиотека password-hash реализует простой механизм:

  • создание хэша пароля с солью;
  • проверка введённого пароля по сохранённому хэшу;
  • использование встроенных алгоритмов (обычно SHA1/MD5 с солью в учебных примерах, либо более безопасные варианты в обёртках).

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


Подключение зависимостей в Express.js приложении

Типичная структура Express-проекта с авторизацией включает:

  • Express для маршрутизации;
  • body-parser или встроенный express.json() для обработки тела запроса;
  • password-hash для работы с паролями;
  • условную модель пользователя (в реальных проектах — база данных).

Пример установки:

npm install express password-hash

Инициализация приложения:

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

const app = express();

app.use(express.json());

Логика регистрации пользователя

Регистрация — это процесс создания нового пользователя и сохранения его пароля в виде хэша.

Основная последовательность действий:

  1. Получение логина и пароля из запроса.
  2. Проверка существования пользователя.
  3. Хэширование пароля.
  4. Сохранение пользователя в хранилище.

Пример реализации:

const users = [];

app.post('/register', (req, res) => {
    const { username, password } = req.body;

    const existingUser = users.find(u => u.username === username);
    if (existingUser) {
        return res.status(400).json({ message: 'Пользователь уже существует' });
    }

    const hashedPassword = passwordHash.generate(password);

    const newUser = {
        id: Date.now(),
        username,
        password: hashedPassword
    };

    users.push(newUser);

    res.status(201).json({ message: 'Пользователь создан' });
});

Важные аспекты хэширования при регистрации

При генерации хэша password-hash автоматически добавляет соль. Это означает:

  • одинаковые пароли у разных пользователей будут иметь разные хэши;
  • атаки по радужным таблицам становятся неэффективными;
  • нет необходимости вручную управлять солью.

Пример результата:

sha1$5b1c2d$3f8a1e9c...

Структура включает алгоритм, соль и сам хэш.


Логика авторизации (логин пользователя)

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

Ключевая ошибка новичков — попытка сравнивать строки напрямую. Это невозможно, так как пароль не хранится в открытом виде.

Правильный подход:

app.post('/login', (req, res) => {
    const { username, password } = req.body;

    const user = users.find(u => u.username === username);

    if (!user) {
        return res.status(400).json({ message: 'Пользователь не найден' });
    }

    const isValid = passwordHash.verify(password, user.password);

    if (!isValid) {
        return res.status(401).json({ message: 'Неверный пароль' });
    }

    res.json({ message: 'Успешный вход' });
});

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

Функция verify выполняет несколько операций:

  • извлекает соль из сохранённого хэша;
  • применяет тот же алгоритм к введённому паролю;
  • сравнивает полученный результат с сохранённым хэшем.

Важно, что сравнение происходит безопасным способом, предотвращающим утечки через timing attacks в базовой реализации.


Разделение ответственности в архитектуре Express

Даже в небольшом приложении рекомендуется разделять логику:

  • маршруты (routes) — принимают HTTP-запросы;
  • сервисы (services) — содержат бизнес-логику;
  • модели (models) — отвечают за хранение данных.

Пример сервиса пользователей:

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

class UserService {
    constructor() {
        this.users = [];
    }

    createUser(username, password) {
        const hashedPassword = passwordHash.generate(password);

        const user = {
            id: Date.now(),
            username,
            password: hashedPassword
        };

        this.users.push(user);
        return user;
    }

    authenticate(username, password) {
        const user = this.users.find(u => u.username === username);
        if (!user) return null;

        const isValid = passwordHash.verify(password, user.password);
        if (!isValid) return null;

        return user;
    }
}

module.exports = new UserService();

Использование сервиса в маршрутах

const express = require('express');
const userService = require('./userService');

const router = express.Router();

router.post('/register', (req, res) => {
    const { username, password } = req.body;

    const user = userService.createUser(username, password);

    res.status(201).json({ id: user.id, username: user.username });
});

router.post('/login', (req, res) => {
    const { username, password } = req.body;

    const user = userService.authenticate(username, password);

    if (!user) {
        return res.status(401).json({ message: 'Ошибка авторизации' });
    }

    res.json({ message: 'Успешный вход', user: user.username });
});

module.exports = router;

Обработка ошибок и типовые проблемы

При работе с хэшированием часто встречаются ошибки:

1. Повторное хэширование уже захэшированного пароля

  • приводит к невозможности входа;
  • решение: хэшировать только при регистрации или смене пароля.

2. Сравнение строк вместо verify

  • всегда возвращает false;
  • нарушает принцип безопасности.

3. Отсутствие проверки существования пользователя

  • может привести к ошибкам обращения к undefined.

Безопасность при использовании password-hash

Хотя библиотека упрощает работу, она не относится к современным криптографическим стандартам уровня bcrypt или argon2. В учебных проектах этого достаточно, но важно учитывать:

  • алгоритмы SHA1/MD5 устарели для продакшена;
  • отсутствие настройки стоимости вычислений (cost factor);
  • ограниченная защита от GPU-атак.

В реальных системах чаще применяются:

  • bcrypt;
  • argon2;
  • scrypt.

Поток данных при регистрации и логине

Регистрация:

клиент → Express → generate(password) → хэш → хранение

Логин:

клиент → Express → verify(password, hash) → результат → ответ

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


Интеграция с middleware в Express

Проверка авторизации может быть вынесена в middleware:

function authMiddleware(req, res, next) {
    const { username, password } = req.headers;

    const user = userService.authenticate(username, password);

    if (!user) {
        return res.status(401).json({ message: 'Доступ запрещён' });
    }

    req.user = user;
    next();
}

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

app.get('/profile', authMiddleware, (req, res) => {
    res.json({ user: req.user.username });
});

Масштабирование подхода

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

  • добавляются токены (JWT);
  • вводится база данных (PostgreSQL, MongoDB);
  • используется middleware для проверки токенов;
  • парольное хэширование остаётся на уровне сервиса пользователей.

password-hash в таких системах может быть заменён, но логика разделения остаётся идентичной: регистрация → хэширование → хранение, логин → проверка → выдача сессии.


Итоговая модель взаимодействия компонентов

Express приложение с password-hash обычно строится по следующей схеме:

  • HTTP слой (routes)
  • бизнес-логика (services)
  • слой данных (in-memory или DB)
  • криптографический слой (password-hash)

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