Аутентификация и безопасность REST API в Nest.js с JWT и MongoDB

Nest.js — это каркас (framework) поверх Node.js и Express.js, который предоставляет структурированную архитектуру, модульность и упрощённые абстракции. Express даёт свободу и лёгкость, но не диктует структуру — Nest.js устраняет разрыв, предлагая шаблон проектирования, который облегчает поддержку и масштабирование API.
Что вы получите из этого руководства
- Готовый шаблон проекта Nest.js с подключённой MongoDB через Mongoose.
- Реализацию регистрации пользователей с хешированием пароля (bcrypt).
- Вход (login) с выдачей JWT и защитой маршрутов через Guard.
- Плейбук по безопасности: хранение секретов, расширение авторизации, рекомендации по тестированию.
Важно: в примерах используется JSON Web Token (JWT) для аутентификации. JWT удобен и распространён, но требует внимательного управления сроками жизни, секретами и политиками отзыва/обновления токенов.
Содержание
- Настройка проекта Nest.js
- Подключение MongoDB (Mongoose)
- Модель пользователя и сервис аутентификации
- Guard для проверки JWT
- Контроллер API и модули
- Практики безопасности и hardening
- Альтернативные подходы и когда JWT не подходит
- Чек-листы и тестовые сценарии
- Краткое резюме
Требования и предварительная подготовка
- Node.js (рекомендуется LTS) и npm.
- Локальная или облачная MongoDB (atlas или другая).
- Установленный Nest CLI (опционально, можно создавать файлы вручную).
Настройка проекта Nest.js
Установите Nest CLI глобально и создайте проект:
npm i -g @nestjs/cli
nest new nest-jwt-apiВыберите npm как пакетный менеджер, дождитесь установки зависимостей, затем перейдите в каталог проекта и запустите его:
cd nest-jwt-api
npm run startУстановите дополнительные зависимости, которые потребуются в проекте:
npm install mongodb mongoose @nestjs/mongoose @types/bcrypt bcrypt jsonwebtoken @nestjs/jwtПримечание: пакет @types/bcrypt нужен только для TypeScript-типов при использовании нативного bcrypt; альтернативно можно использовать bcryptjs.
Подключение MongoDB
Создайте базу данных (локально или в облаке) и получите строку подключения (URI). Создайте файл .env в корне проекта и поместите туда:
MONGO_URI="connection string"Откройте src/app.module.ts и добавьте подключение Mongoose и ConfigModule:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserAuthModule } from './user-auth/user-auth.module';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.env',
isGlobal: true,
}),
MongooseModule.forRoot(process.env.MONGO_URI),
UserAuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}Модуль ConfigModule сделает переменные окружения доступными по всему приложению, а MongooseModule установит соединение с MongoDB.
Создание модуля аутентификации пользователя
Для структурирования кода создайте модуль user-auth через CLI (или вручную):
nest g module user-authCLI автоматически добавит модуль в app.module.ts. Далее создадим схему пользователя.
Схема пользователя (Mongoose)
Создайте файл src/user-auth/schemas/user-auth.schema.ts со следующим кодом:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema({ timestamps: true })
export class User {
@Prop()
username: string;
@Prop()
password: string;
}
export type UserDocument = User & Document;
export const UserSchema = SchemaFactory.createForClass(User);Эта схема хранит username и захешированный password. Поле timestamps добавляет createdAt и updatedAt автоматически.
Сервис аутентификации пользователей
Сгенерируйте сервис:
nest g service user-authОткройте src/user-auth/user-auth.service.ts и добавьте логику регистрации, входа и получения пользователей:
import { Injectable, NotFoundException, Logger, UnauthorizedException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './schemas/user-auth.schema';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class UserAuthService {
private readonly logger = new Logger(UserAuthService.name);
constructor(@InjectModel(User.name) private userModel: Model, private jwtService: JwtService) {}
async registerUser(username: string, password: string): Promise<{ message: string }> {
try {
const hash = await bcrypt.hash(password, 10);
await this.userModel.create({ username, password: hash });
return { message: 'User registered successfully' };
} catch (error) {
throw new Error('An error occurred while registering the user');
}
}
async loginUser(username: string, password: string): Promise {
try {
const user = await this.userModel.findOne({ username });
if (!user) {
throw new NotFoundException('User not found');
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
throw new UnauthorizedException('Invalid login credentials');
}
const payload = { userId: user._id };
const token = this.jwtService.sign(payload);
return token;
} catch (error) {
console.log(error);
throw new UnauthorizedException('An error occurred while logging in');
}
}
async getUsers(): Promise {
try {
const users = await this.userModel.find({});
return users;
} catch (error) {
this.logger.error(`An error occurred while retrieving users: ${error.message}`);
throw new Error('An error occurred while retrieving users');
}
}
} Ключевые моменты:
- Пароли хешируются с saltRounds = 10.
- JWT создаётся через JwtService.
- Возвращается строка токена при логине.
Guard для проверки JWT
Чтобы защитить маршруты, создайте Guard, который извлекает токен из заголовка Authorization и верифицирует его.
Создайте src/user-auth/auth.guard.ts:
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { secretKey } from './config';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: secretKey.secret,
});
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
} Создайте также конфигурацию секрета в src/user-auth/config.ts:
export const secretKey = {
secret: 'SECTRET VALUE.',
};Важно: никогда не храните секрет напрямую в коде в продакшене. Используйте переменные окружения, секреты провайдера, Vault или другие безопасные хранилища.
Контроллер API
Сгенерируйте контроллер:
nest g controller user-authВ контроллере реализуйте маршруты регистрации, логина и получения списка пользователей. Примените UseGuards(AuthGuard) к маршруту получения пользователей, чтобы ограничить доступ только авторизованным.
(Код контроллера можно взять из репозитория проекта; здесь важно привязать сервис и Guard внутри модуля.)
Обновление user-auth.module.ts
Файл src/user-auth/user-auth.module.ts должен подключать MongooseFeature и JwtModule:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { UserAuthController } from './user-auth.controller';
import { UserAuthService } from './user-auth.service';
import { MongooseModule } from '@nestjs/mongoose';
import { UserSchema } from './schemas/user-auth.schema';
import { secretKey } from './config';
@Module({
imports: [
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
JwtModule.register({
secret: secretKey.secret,
signOptions: { expiresIn: '1h' },
}),
],
controllers: [UserAuthController],
providers: [UserAuthService],
})
export class UserAuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {}
}После этого запустите сервер и протестируйте маршруты через Postman или curl:
npm run startПрактики безопасности и hardening
Важно рассматривать безопасность на нескольких уровнях:
- Хранение секретов: используйте переменные окружения, менеджер секретов (HashiCorp Vault, AWS Secrets Manager).
- Срок жизни токенов: короткий access token (например, 15–60 минут) + refresh token с более длительным сроком жизни.
- Отзыв токенов: хранение blacklist/revocation list или использование версий/идентификаторов в базе.
- HTTPS: все клиент-сервер соединения должны быть через TLS.
- Ограничение попыток входа: rate limiting, блокировка по IP или аккаунту.
- Валидация входных данных: Joi или class-validator для DTO.
- CSP, CORS и заголовки безопасности: helmet, настройка CORS по необходимости.
- Логирование и мониторинг: следите за подозрительными попытками входа.
Важно: JWT — это утверждение о подлинности. Сам по себе JWT не даёт полной защиты от компрометации учётных записей или токенов.
Альтернативные подходы и когда JWT не подходит
- Сессионная аутентификация (server-side sessions) лучше подходит, если нужен мгновенный отзыв сессии и минимальная сложность реализации.
- OAuth2 / OpenID Connect — когда требуется сторонняя авторизация, делегирование или единая точка входа (SSO).
- Mutual TLS — для машин-к-машине аутентификации в высокозащищённых средах.
Когда JWT может не подходить:
- Нужно мгновенно отзывать доступ большого количества пользователей без дополнительного хранилища.
- Требуется крайне строгий контроль сессий на сервере и высокая чувствительность к компрометации токенов.
Ментальные модели и эвристики
- Модель доверия: JWT = кратковременное доверие. Не храните в токене чувствительную информацию.
- Разделение ролей: аутентификация (кто вы) и авторизация (что вы можете делать).
- Defense in Depth: используйте несколько слоёв защиты — валидация, шифрование, лимитирование, аудит.
Чек-лист перед развертыванием
- Секреты вынесены из кода в безопасное хранилище.
- HTTPS настроен для всех входящих соединений.
- CORS ограничен нужными доменами.
- Логи настроены, нет утечек паролей в логах.
- Пароли хранятся в виде хешей с подходящим rounds.
- Тесты на основные сценарии: регистрация, логин, доступ защищённых маршрутов.
- Политики восстановления/сброса пароля реализованы.
Тестовые сценарии и критерии приёмки
Критерии приёмки (минимум):
- Регистрация возвращает 200 и сообщение успеха при валидных данных.
- Логин возвращает валидный JWT при корректных учётных данных.
- Доступ к /users без токена возвращает 401.
- Доступ к /users с корректным токеном возвращает список пользователей.
Тест-кейсы (примеры):
- Попытка регистрации с уже существующим username.
- Логин с неверным паролем — должен вернуть 401.
- Использование просроченного токена — должен вернуть 401.
Мини-методология для доработки в продакшн
- Провести аудит зависимостей и обновить пакеты до LTS-версий.
- Перенести секреты в безопасное хранилище.
- Добавить refresh-token механизм с хранением в базе.
- Настроить rate-limiter (например, express-rate-limit/redis).
- Написать интеграционные тесты и CI-пайплайн.
Примеры конфигураций и сниппеты
- Рекомендация по expiration: access token 15–60 минут, refresh token 7–30 дней.
- bcrypt saltRounds: 10–12 для балансa между безопасностью и производительностью.
Примеры отказов и крайние случаи
- Если секрет JWT случайно утёк — следует сменить secret и реализовать отозванные токены (черный список) или поддерживать версию ключа в payload.
- Если база пользователей удалена — резервные копии и политика восстановления крайне важны.
Приватность и соответствие требованиям (GDPR)
- Сохраняйте минимально необходимые персональные данные.
- У пользователей должно быть право запроса удаления их данных (Right to be forgotten).
- Логи и бэкапы не должны содержать необработанные пароли.
Важно: юридические требования зависят от юрисдикции — проконсультируйтесь с юридическим отделом для соответствия GDPR/локальным законам.
Резюме
Этот материал показал, как быстро начать с Nest.js и MongoDB, реализовать базовую регистрацию/логин с JWT и защитой маршрутов через Guard. Но сама базовая реализация — только начало: для безопасного продакшна необходимы дополнительные меры — управление секретами, политика обновления токенов, мониторинг и тестирование.
Важно: JWT удобен, но не заменяет комплексной политики безопасности. Проверяйте архитектуру и применяйте defense-in-depth.
Ключевые шаги далее: вынести секреты, добавить refresh token, настроить rate limiting, покрыть тестами и сделать аудит безопасности.
FAQ
Вопрос: Нужно ли использовать refresh токены?
Да. Refresh токены позволяют выдавать короткоживущие access токены и уменьшить риск, если access токен скомпрометирован.
Вопрос: Где хранить JWT на клиенте?
Для SPA чаще используют HttpOnly cookies (без доступа через JS) для повышения защиты от XSS; LocalStorage проще, но уязвим к XSS.
Вопрос: Как отозвать JWT до истечения срока?
Нужно хранить список отозванных токенов (blacklist) или версионировать ключи/учётные записи и проверять соответствие версии при верификации.
Краткое резюме:
- Nest.js даёт структуру и модульность, Mongoose упрощает работу с MongoDB.
- JWT реализуется через JwtModule и Guard для защиты маршрутов.
- В продакшне критичны управление секретами, refresh-токены, rate-limiting и аудит.
Похожие материалы
Как найти приложения для Android Wear 2.0
Как размыть фон в Canva: 3 простых способа
Установить и протестировать Windows 10 S
RRoD на Xbox 360: как устранить и диагностировать
Как проверить версию Ubuntu