Пошук уроків, статей та іншого контенту
Створите guard для перевірки автентифікації користувача та передачі його даних у запит.
Guard у NestJS — це клас, який вирішує, чи може запит продовжити виконання. Він реалізує інтерфейс CanActivate і виконується до обробника контролера.
Guard автентифікації зазвичай:
Отримує токен із заголовка запиту.
Перевіряє токен.
Дістає з нього дані користувача.
Записує користувача в request.user.
Дозволяє виконання контролера або повертає помилку 401 Unauthorized.
Після цього контролер може отримати автентифікованого користувача з об’єкта запиту.
Створимо guard, який перевіряє JWT у заголовку:
Authorization: Bearer <token>Для роботи прикладу потрібен пакет @nestjs/jwt:
npm install @nestjs/jwtСтворимо файл src/auth/auth.guard.ts:
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
export interface AuthUser {
sub: number;
email: string;
}
type AuthenticatedRequest = Request & {
user?: AuthUser;
};
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request =
context.switchToHttp().getRequest<AuthenticatedRequest>();
const token = this.extractToken(request);
if (!token) {
throw new UnauthorizedException('Токен не знайдено');
}
try {
const payload = await this.jwtService.verifyAsync<AuthUser>(token);
request.user = payload;
return true;
} catch {
throw new UnauthorizedException('Недійсний або прострочений токен');
}
}
private extractToken(request: Request): string | undefined {
const authorization = request.headers.authorization;
if (!authorization) {
return undefined;
}
const [type, token] = authorization.split(' ');
if (type !== 'Bearer' || !token) {
return undefined;
}
return token;
}
}canActivateМетод canActivate отримує ExecutionContext. Для HTTP-запиту з нього можна дістати об’єкт запиту:
const request = context.switchToHttp().getRequest();Заголовок authorization має формат:
Bearer eyJhbGciOiJIUzI1NiIs...Виклик split(' ') розділяє його на дві частини:
Bearer — тип авторизації;
токен — значення JWT.
Метод verifyAsync перевіряє:
правильність підпису;
термін дії токена, якщо в ньому є exp;
відповідність секретного ключа.
Якщо перевірка успішна, метод повертає payload токена. Ми записуємо його в request.user:
request.user = payload;Повернення true дозволяє NestJS передати запит у контролер.
JwtModuleGuard використовує JwtService, тому JwtModule потрібно імпортувати в модуль.
Файл src/auth/auth.module.ts:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthGuard } from './auth.guard';
@Module({
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET ?? 'development-secret',
}),
],
providers: [AuthGuard],
exports: [AuthGuard],
})
export class AuthModule {}У реальному застосунку секрет потрібно зберігати в змінних середовища. Значення 'development-secret' підходить лише для локальної демонстрації.
Guard можна застосувати до окремого маршруту за допомогою @UseGuards.
Файл src/users/users.controller.ts:
import {
Controller,
Get,
Req,
UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { AuthGuard, AuthUser } from '../auth/auth.guard';
type AuthenticatedRequest = Request & {
user: AuthUser;
};
@Controller('users')
export class UsersController {
@Get('me')
@UseGuards(AuthGuard)
getCurrentUser(@Req() request: AuthenticatedRequest) {
return {
id: request.user.sub,
email: request.user.email,
};
}
}Тепер маршрут GET /users/me вимагає валідний JWT.
Приклад запиту:
GET /users/me
Authorization: Bearer <valid-jwt>Приклад відповіді:
{
"id": 42,
"email": "user@example.com"
}Дані id та email були отримані з payload токена через request.user.
Модуль із контролером повинен імпортувати AuthModule, щоб NestJS міг знайти AuthGuard.
Файл src/users/users.module.ts:
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { AuthModule } from '../auth/auth.module';
@Module({
imports: [AuthModule],
controllers: [UsersController],
})
export class UsersModule {}Кореневий модуль:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [AuthModule, UsersModule],
})
export class AppModule {}Нижче наведено приклад контролера, який створює демонстраційний токен і використовує його для доступу до захищеного маршруту.
Маршрут
/auth/demo-tokenпотрібен лише для перевірки guard у навчальному прикладі. У реальному застосунку токен видається після успішної перевірки логіна та пароля.
Файл src/auth/auth.controller.ts:
import { Controller, Get } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Controller('auth')
export class AuthController {
constructor(private readonly jwtService: JwtService) {}
@Get('demo-token')
createDemoToken() {
const payload = {
sub: 42,
email: 'user@example.com',
};
return {
access_token: this.jwtService.sign(payload),
};
}
}Оновлений auth.module.ts:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthGuard } from './auth.guard';
@Module({
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET ?? 'development-secret',
}),
],
controllers: [AuthController],
providers: [AuthGuard],
exports: [AuthGuard],
})
export class AuthModule {}Після запуску застосунку послідовність перевірки буде такою:
Виконати GET /auth/demo-token.
Скопіювати значення access_token.
Передати його в заголовку Authorization.
Виконати GET /users/me.
Приклад заголовка:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Якщо токен правильний, AuthGuard запише його payload у request.user, а контролер поверне дані користувача.
Якщо guard потрібно застосувати до всіх методів контролера, його можна вказати на рівні класу:
import {
Controller,
Get,
Req,
UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { AuthGuard, AuthUser } from '../auth/auth.guard';
type AuthenticatedRequest = Request & {
user: AuthUser;
};
@Controller('users')
@UseGuards(AuthGuard)
export class UsersController {
@Get('me')
getCurrentUser(@Req() request: AuthenticatedRequest) {
return request.user;
}
@Get('settings')
getSettings(@Req() request: AuthenticatedRequest) {
return {
userId: request.user.sub,
settings: {},
};
}
}У цьому випадку обидва маршрути контролера вимагатимуть автентифікацію.
Якщо guard потрібен лише одному маршруту, краще залишити @UseGuards(AuthGuard) безпосередньо над цим маршрутом.
Guard повинен повертати помилку, а не просто false, якщо користувач не автентифікований:
throw new UnauthorizedException('Токен не знайдено');NestJS перетворить це на HTTP-відповідь:
{
"statusCode": 401,
"message": "Токен не знайдено",
"error": "Unauthorized"
}Так само обробляється токен із неправильним підписом або завершеним терміном дії:
{
"statusCode": 401,
"message": "Недійсний або прострочений токен",
"error": "Unauthorized"
}providersЯкщо AuthGuard не зареєстрований у модулі, NestJS не зможе створити його залежність JwtService.
Переконайтеся, що в модулі є:
@Module({
providers: [AuthGuard],
exports: [AuthGuard],
})
export class AuthModule {}JwtModule не імпортованоJwtService надається модулем JwtModule. Він має бути імпортований у тому модулі, де створюється AuthGuard:
@Module({
imports: [
JwtModule.register({
secret: 'development-secret',
}),
],
})
export class AuthModule {}Guard очікує формат:
Authorization: Bearer <token>Такі варіанти не відповідають очікуваному формату:
Authorization: <token>
Authorization: Basic <token>
Authorization: BearerПеревірка токена сама по собі не передає користувача до контролера. Потрібно явно виконати:
request.user = payload;Без цього request.user буде undefined.
Якщо контролер очікує request.user.sub, токен повинен містити поле sub:
const payload = {
sub: 42,
email: 'user@example.com',
};Назви та структура полів payload мають бути узгоджені між кодом, який створює токен, guard і контролерами.
Guard реалізує інтерфейс CanActivate.
ExecutionContext дає доступ до HTTP-запиту.
JWT можна отримати із заголовка Authorization.
JwtService.verifyAsync перевіряє токен і повертає його payload.
Дані автентифікованого користувача передаються через request.user.
@UseGuards(AuthGuard) захищає маршрут або весь контролер.
Для відсутнього чи недійсного токена потрібно повертати UnauthorizedException.