Аутентификация в Node.js + PostgreSQL с Passport.js

Как разработчик, вы отвечаете за защиту данных пользователей через корректную аутентификацию. Passport.js помогает управлять проверкой подлинности в приложении Node + PostgreSQL и снижает количество повторяющегося кода при реализации логина и регистрации.
Что вы получите
- Рабочий набор файлов и маршрутов для регистрации и входа пользователя.
- Безопасное хранение паролей с bcrypt.
- Примеры вспомогательных функций для работы с БД и их тестирования.
- Практические советы по безопасности и развертыванию.
Важно: пример ориентирован на учебные и начальные продакшен-решения. Перед выводом в продакшен применяйте дополнительные меры безопасности (TLS, переменные окружения, ограничение прав БД и т. п.).
Создание таблицы users
Для аутентификации понадобятся поля email и password. В psql создайте базу данных nodeapp:
CREATE DATABASE nodeapp;
Далее создайте таблицу пользователей:
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email CHAR(128),
password CHAR(60)
);
Этот код создаст таблицу с полями email, password и автоинкрементным id. Примечание: в реальной системе имеет смысл использовать типы VARCHAR и уникальный индекс на email, например “email VARCHAR(255) UNIQUE”.
Создание Node-сервера
Node.js — это серверная среда для запуска JavaScript. Для простоты создаём сервер на Express.
Создайте папку и инициализируйте проект:
mkdir postgres-auth
npm init -y
Установите Express:
npm install express
Создайте файл index.js и добавьте:
const express = require("express");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.listen(3000, () => console.log("Listening on port 3000"));
Запуск этого кода стартует сервер и выведет в консоль:
Listening on port 3000Совет: при разработке используйте nodemon для авто-перезапуска: npm install –save-dev nodemon.
Подключение к PostgreSQL
Для работы с PostgreSQL используем node-postgres (pg).
Установите драйвер:
npm install pg
Создайте файл db.js и подключите клиента:
const { Client } = require("pg");
const { user, host, database, password, port } = require("./dbConfig");
const client = new Client({
user,
host,
database,
password,
port,
});
client.connect();
module.exports = client;
Файл dbConfig.js хранит параметры подключения (в продакшене используйте переменные окружения или менеджер секретов):
module.exports = {
user: "postgres",
host: "localhost",
database: "nodeapp",
password: "yourPassword",
port: 5432,
};
Важно: не храните пароли и секреты в коде. Используйте .env и библиотеку dotenv для локальной разработки.
Вспомогательные функции для работы с базой
Разделение логики работы с БД на функции упрощает тестирование и поддержку. Для регистрации нам понадобятся функции:
- Проверка существования email.
- Создание пользователя.
Создайте helper.js и импортируйте клиент из db.js:
const client = require("./db.js")
Добавьте функцию emailExists():
const emailExists = async (email) => {
const data = await client.query("SELECT * FROM users WHERE email=$1", [
email,
]);
if (data.rowCount == 0) return false;
return data.rows[0];
};
Эта функция возвращает false, если email не найден, иначе — объект пользователя.
Добавим createUser():
const createUser = async (email, password) => {
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(password, salt);
const data = await client.query(
"INSERT INTO users(email, password) VALUES ($1, $2) RETURNING id, email, password",
[email, hash]
);
if (data.rowCount == 0) return false;
return data.rows[0];
};
Здесь пароль хешируется перед сохранением. Не храните пароли в открытом виде.
Установите bcryptjs:
npm install bcryptjs
Импортируйте bcryptjs в helper.js:
const bcrypt = require("bcryptjs")
Добавьте matchPassword() для сравнения пароля при входе:
const matchPassword = async (password, hashPassword) => {
const match = await bcrypt.compare(password, hashPassword);
return match
};
Экспортируйте функции:
module.exports = { emailExists, createUser, matchPassword };
Совет по безопасности: используйте VARCHAR вместо CHAR для паролей и email. CHAR фиксирует длину и приводит к пробелам на конце.
Настройка Passport
Passport — middleware для Node, поддерживающий множество стратегий аутентификации. Для локальной аутентификации используем passport-local.
Установите Passport и стратегию:
npm install passport
npm install passport-local
Создайте файл passportConfig.js и импортируйте LocalStrategy и функции из helper.js:
const LocalStrategy = require("passport-local");
const { emailExists, createUser, matchPassword } = require("./helper");
Добавьте стратегию для регистрации:
module.exports = (passport) => {
passport.use(
"local-signup",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
const userExists = await emailExists(email)
if (userExists) {
return done(null, false);
}
const user = await createUser(email, password);
return done(null, user);
} catch (error) {
done(error);
}
}
)
);
}
Passport ожидает поля username и password, поэтому мы указываем usernameField: “email”.
Добавим стратегию для логина:
module.exports = (passport) => {
passport.use(
"local-signup",
new LocalStrategy(
// sign up
)
);
passport.use(
"local-login",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
const user = await emailExists(email);
if (!user) return done(null, false);
const isMatch = await matchPassword(password, user.password);
if (!isMatch) return done(null, false);
return done(null, {id: user.id, email: user.email});
} catch (error) {
return done(error, false);
}
}
)
);
};
Стратегия для входа ищет пользователя по email и проверяет пароль через matchPassword().
Создание маршрутов API
Окончательно создадим маршруты для регистрации и входа:
Импортируем и настраиваем Passport в файле server.js (или index.js):
const passport = require("passport");
require("./passportConfig")(passport);
Добавьте маршруты:
app.post(
"/auth/signup",
passport.authenticate("local-signup", { session: false }),
(req, res, next) => {
res.json({
user: req.user,
});
}
);
app.post(
"/auth/login",
passport.authenticate("local-login", { session: false }),
(req, res, next) => {
res.json({ user: req.user });
}
);
Обе конечные точки возвращают JSON с объектом user при успешной аутентификации.
Примечание: здесь session: false — это вариант без сессий. Для использования сессий добавьте express-session и реализуйте serializeUser/deserializeUser.
Тестирование API и юнит-тесты
Тесты упрощают поиск ошибок на ранних этапах. Для Node-приложений часто используют Jest и supertest для интеграционных тестов маршрутов.
Минимальные сценарии тестирования:
- Успешная регистрация нового пользователя.
- Ошибка при попытке зарегистрировать уже существующий email.
- Успешный вход корректным email+паролем.
- Ошибка входа при неверном пароле.
- Валидация формата email и длины пароля.
Пример тест-кейса (псевдокод, не полный файл):
// using supertest + jest
it('should register a new user', async () => {
const res = await request(app)
.post('/auth/signup')
.send({ email: 'test@example.com', password: 'pass1234' });
expect(res.statusCode).toBe(200);
expect(res.body.user).toHaveProperty('id');
});
Рекомендации по безопасности и жёсткая настройка
Важно: аутентификация — критический компонент. Ниже базовый чек-лист жёсткой настройки:
- Храните секреты в переменных окружения (process.env) или менеджере секретов.
- Используйте HTTPS (TLS) для всех клиентских запросов.
- Устанавливайте и обновляйте зависимости (npm audit, Dependabot).
- Ограничивайте права пользователя БД: предоставляйте только нужные разрешения (SELECT, INSERT для таблицы users).
- Включите защиту от перебора паролей: rate limiting, account lockout, CAPTCHA при попытках входа.
- Подтверждение email при регистрации и сброс пароля через одноразовые токены.
- Храните пароли с современными параметрами: bcrypt с salt rounds >= 10 (регулируйте по мощности сервера).
- Защита заголовков: helmet middleware.
- CSRF: применимо для cookie-based аутентификации.
- Валидация входных данных: проверяйте формат email, минимальную длину и символы пароля.
- Логи: логируйте попытки входа, но не храните пароли в логах.
- Подключение к БД по TLS, если БД находится на удалённом хосте.
Примеры полезных пакетов:
npm install dotenv helmet express-rate-limit
Пример использования helmet и rate limit в Express:
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
app.use(helmet());
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
Альтернативные подходы
- JWT (JSON Web Tokens): альтернатива сессиям. Хорош для stateless API. Храните refresh-токены безопасно.
- OAuth / социальная аутентификация: passport поддерживает множество внешних провайдеров.
- Использование готовых сервисов (Auth0, Firebase Auth) для уменьшения операционной нагрузки.
Когда локальная стратегия passport-local подходит: простые приложения и внутренняя аутентификация. Когда не подходит: когда нужно масштабировать распределённую систему с безгосударственными сервисами и короткими токенами — чаще выбирают JWT.
Методология внедрения (мини-план)
- Разработать схему БД и гарантировать уникальность email.
- Настроить локальную среду и .env.
- Реализовать helper-функции и покрыть их юнит-тестами.
- Подключить Passport, добавить стратегии signup/login.
- Добавить middleware безопасности (helmet, rate limiter).
- Написать интеграционные тесты для маршрутов.
- Настроить CI, автоматические тесты и проверку зависимостей.
- Развернуть на staging, прогнать нагрузочное тестирование и тесты безопасности.
- Перейти в продакшен с мониторингом и алертами.
Риски и меры смягчения
- Риск: утечка секретов. Мера: убрать секреты из репозитория, использовать vault.
- Риск: перебор пароля. Мера: rate limiting, блокировки, мониторинг.
- Риск: SQL-инъекции. Мера: используйте параметризированные запросы (пример выше использует $1).
- Риск: слабые пароли. Мера: требовать минимальную длину и проверять по списку общих паролей.
Роли и чек-листы
Разработчик:
- Написать helper-функции и покрыть тестами.
- Добавить обработку ошибок.
- Проверить валидацию входных данных.
DevOps:
- Настроить переменные окружения и секреты.
- Убедиться в TLS между клиентом и сервером.
- Ограничить права БД.
QA:
- Написать тест-кейсы для регистрации и логина.
- Проверить обработку ошибок и кейсы краёв.
Security:
- Провести ревью зависимостей.
- Протестировать на уязвимости (SAST/DAST).
Критерии приёмки
- Регистрация возвращает объект user с id и email.
- Попытка регистрации с существующим email возвращает ошибку/статус 4xx.
- Успешный вход возвращает user без пароля.
- Никакой пароль не логируется и не возвращается клиенту.
- Тесты на основные сценарии проходят в CI.
Глоссарий (одна строка, определения)
- Passport: middleware для Node.js, реализующий стратегии аутентификации.
- passport-local: стратегия Passport для логина по username/password.
- bcrypt: алгоритм для хеширования паролей с солью.
- JWT: JSON Web Token, формат токенов для stateless-аутентификации.
Шпаргалка команд и конфигурация
.env (пример):
DB_USER=postgres
DB_HOST=localhost
DB_NAME=nodeapp
DB_PASS=yourPassword
DB_PORT=5432
PORT=3000
Полезные команды:
npm install express pg passport passport-local bcryptjs dotenv helmet express-rate-limit
npm install --save-dev jest supertest nodemonПримеры отказов и когда подход не подходит
- Если ваше приложение требует single-sign-on (SSO) для корпоративной среды, используйте OAuth2/OpenID Connect.
- Для распределённых микросервисов с масштабируемым безгосударственным доступом JWT чаще подходит лучше, чем сессии.
Короткое резюме
- Passport.js с passport-local позволяет быстро добавить локальную аутентификацию в Node + PostgreSQL.
- Хешируйте пароли с bcrypt и храните секреты вне кода.
- Добавьте rate limiting, helmet и валидацию для повышения безопасности.
Важно: перед запуском в продакшен поставьте дополнительные уровни защиты и тестирование.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone