Пошук уроків, статей та іншого контенту
Підключите Passport до NestJS і налаштуєте стратегії для стандартизованої обробки автентифікації.
Passport — це middleware для Node.js, який стандартизує процес автентифікації за допомогою стратегій.
Стратегія визначає:
звідки отримати облікові дані;
як їх перевірити;
якого користувача передати далі в обробник запиту.
У NestJS Passport інтегрується через пакет @nestjs/passport. Найчастіше використовують:
local — перевірка логіна та пароля під час входу;
jwt — перевірка JWT для захищених маршрутів.
Passport не створює користувачів і не зберігає їх у базі даних. Він лише виконує описану стратегією перевірку та додає результат до запиту.
Для прикладу використаємо локальну стратегію та JWT-стратегію:
npm install @nestjs/passport passport passport-local passport-jwt @nestjs/jwt
npm install --save-dev @types/passport @types/passport-local @types/passport-jwtПісля встановлення структура модуля автентифікації може мати такий вигляд:
src/
auth/
auth.controller.ts
auth.module.ts
auth.service.ts
jwt.strategy.ts
local.strategy.ts
app.module.tsPassportModule підключається до AuthModule. Також підключимо JwtModule, який буде створювати JWT після успішної локальної автентифікації.
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
@Module({
imports: [
PassportModule.register({
defaultStrategy: 'jwt',
}),
JwtModule.register({
secret: process.env.JWT_SECRET ?? 'dev-secret',
signOptions: {
expiresIn: '15m',
},
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}defaultStrategy: 'jwt' означає, що Passport використовуватиме JWT як стратегію за замовчуванням. Водночас у конкретному маршруті можна явно вказати іншу стратегію, наприклад local.
У реальному застосунку секрет потрібно зберігати в змінних середовища, а не записувати безпосередньо в коді.
Підключіть модуль до головного модуля застосунку:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [AuthModule],
})
export class AppModule {}Сервіс відповідає за перевірку користувача та створення токена.
Для стислого прикладу використаємо масив користувачів у пам’яті. У реальному застосунку замість нього буде репозиторій або сервіс роботи з базою даних.
// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
type User = {
id: number;
username: string;
password: string;
};
@Injectable()
export class AuthService {
private readonly users: User[] = [
{
id: 1,
username: 'alice',
password: 'password123',
},
];
constructor(private readonly jwtService: JwtService) {}
async validateUser(
username: string,
password: string,
): Promise<Omit<User, 'password'> | null> {
const user = this.users.find(
(item) => item.username === username && item.password === password,
);
if (!user) {
return null;
}
// Не передаємо пароль до наступних етапів обробки запиту
const { password: _, ...safeUser } = user;
return safeUser;
}
async login(user: Omit<User, 'password'>) {
const payload = {
sub: user.id,
username: user.username,
};
return {
access_token: await this.jwtService.signAsync(payload),
};
}
}У робочому застосунку паролі не можна порівнювати у відкритому вигляді. Їх потрібно зберігати як хеші та перевіряти за допомогою спеціалізованої бібліотеки.
Локальна стратегія використовує passport-local. За замовчуванням вона очікує поля username і password у тілі запиту.
// src/auth/local.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string) {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException('Неправильне ім’я користувача або пароль');
}
return user;
}
}Метод validate() викликається Passport після отримання облікових даних.
Можливі результати:
повернути користувача — автентифікація успішна;
повернути null або викинути помилку — автентифікація неуспішна.
Повернене значення Passport автоматично покладе в request.user.
JWT-стратегія отримує токен із заголовка:
Authorization: Bearer <token>і перевіряє його підпис та термін дії.
// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
type JwtPayload = {
sub: number;
username: string;
};
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET ?? 'dev-secret',
});
}
validate(payload: JwtPayload) {
return {
id: payload.sub,
username: payload.username,
};
}
}Метод validate() у JWT-стратегії отримує вже перевірене тіло токена — payload.
Його результат також буде доступний як request.user. Наприклад, у цьому випадку request.user матиме значення:
{
id: 1,
username: 'alice',
}Для вибору стратегії використовується AuthGuard.
// src/auth/auth.controller.ts
import {
Controller,
Get,
Post,
Request,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
@UseGuards(AuthGuard('local'))
async login(@Request() request: any) {
// request.user додано локальною стратегією
return this.authService.login(request.user);
}
@Get('me')
@UseGuards(AuthGuard('jwt'))
getProfile(@Request() request: any) {
// request.user додано JWT-стратегією
return request.user;
}
}Тепер процес входу має такий вигляд:
Клієнт надсилає POST /auth/login.
У тілі запиту передає:
{
"username": "alice",
"password": "password123"
}AuthGuard('local') запускає LocalStrategy.
LocalStrategy викликає AuthService.validateUser().
Якщо користувача знайдено, він потрапляє в request.user.
Контролер створює JWT і повертає його клієнту.
Для доступу до захищеного маршруту клієнт передає отриманий токен:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Після цього:
AuthGuard('jwt') запускає JwtStrategy;
стратегія дістає токен із заголовка;
перевіряє підпис і термін дії;
викликає validate() з payload;
додає результат у request.user;
дозволяє виконати метод контролера.
Запустіть застосунок:
npm run start:devВиконайте вхід:
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"password123"}'У відповідь буде об’єкт із токеном:
{
"access_token": "..."
}Передайте цей токен до захищеного маршруту:
curl http://localhost:3000/auth/me \
-H "Authorization: Bearer ..."Очікувана відповідь:
{
"id": 1,
"username": "alice"
}Якщо токен відсутній, неправильний або прострочений, NestJS поверне помилку 401 Unauthorized, а метод getProfile() не буде виконано.
Якщо в застосунку є кілька стратегій одного типу, їм можна надати власні імена.
Наприклад:
@Injectable()
export class AdminJwtStrategy extends PassportStrategy(
Strategy,
'admin-jwt',
) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET ?? 'dev-secret',
});
}
validate(payload: JwtPayload) {
return payload;
}
}Тоді маршрут явно використовує цю стратегію:
@UseGuards(AuthGuard('admin-jwt'))
@Get('admin')
getAdminData(@Request() request: any) {
return request.user;
}Назва в PassportStrategy(Strategy, 'admin-jwt') повинна збігатися з назвою в AuthGuard('admin-jwt').
У такій архітектурі кожен компонент має окрему роль:
AuthController описує маршрути входу та захисту;
AuthGuard запускає потрібну стратегію;
LocalStrategy перевіряє облікові дані;
JwtStrategy перевіряє токен;
AuthService працює з користувачами та створює JWT;
JwtModule підписує токени.
Це дозволяє замінити спосіб перевірки облікових даних, не змінюючи логіку контролерів.
providersЯкщо LocalStrategy або JwtStrategy не додано до providers модуля, NestJS не зможе створити цю стратегію.
@Module({
providers: [AuthService, LocalStrategy, JwtStrategy],
})
export class AuthModule {}Для стандартної локальної стратегії потрібно використовувати:
AuthGuard('local')Для JWT:
AuthGuard('jwt')Якщо стратегія має власне ім’я, це ім’я потрібно вказати і в PassportStrategy, і в AuthGuard.
JWT-стратегія з ExtractJwt.fromAuthHeaderAsBearerToken() очікує заголовок:
Authorization: Bearer <token>Передача токена в тілі запиту або без префікса Bearer не буде оброблена цією конфігурацією.
Після перевірки не слід повертати об’єкт із паролем у request.user або JWT payload. У прикладі пароль видаляється перед передачею користувача далі.
Секрет, який використовується JwtModule для створення токена, має збігатися із секретом у JwtStrategy.
Значення на кшталт 'dev-secret' придатне лише для локального прикладу. Для реального застосунку секрет потрібно отримувати з конфігурації або змінних середовища.
Passport підключається до NestJS через @nestjs/passport.
Стратегія описує конкретний спосіб автентифікації.
LocalStrategy перевіряє логін і пароль.
JwtStrategy перевіряє JWT у заголовку Authorization.
AuthGuard('local') і AuthGuard('jwt') запускають відповідні стратегії.
Результат validate() автоматично доступний у request.user.
Після локальної автентифікації сервіс може створити JWT.
Захищені маршрути використовують JWT-стратегію для стандартизованої перевірки запитів.