Пошук уроків, статей та іншого контенту
Застосуєте guards для захисту маршрутів і перевірки автентифікації перед виконанням обробників.
Guard — це клас, який визначає, чи можна виконувати обробник маршруту. Guards запускаються після middleware та перед викликом методу контролера.
Guard зручний для перевірок, які мають дозволити або заборонити доступ:
чи автентифікований користувач;
чи має користувач потрібну роль;
чи можна виконувати певну операцію.
Guard повинен реалізувати інтерфейс CanActivate і метод canActivate():
canActivate(context: ExecutionContext): booleanМетод може повернути:
true — виконання маршруту дозволено;
false — доступ заборонено;
Promise<boolean> або Observable<boolean> — для асинхронних перевірок;
виняток — наприклад, UnauthorizedException.
Розглянемо guard, який перевіряє заголовок Authorization. Для спрощення прикладу валідним буде токен demo-token.
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
type AuthenticatedRequest = Request & {
user?: {
id: number;
username: string;
};
};
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request =
context.switchToHttp().getRequest<AuthenticatedRequest>();
const authorization = request.headers.authorization;
if (typeof authorization !== 'string') {
throw new UnauthorizedException('Потрібен токен автентифікації');
}
const [scheme, token] = authorization.split(' ');
if (scheme !== 'Bearer' || token !== 'demo-token') {
throw new UnauthorizedException('Недійсний токен автентифікації');
}
// Зберігаємо дані користувача для подальшого використання в обробнику
request.user = {
id: 1,
username: 'alice',
};
return true;
}
}@Injectable() дозволяє NestJS створити цей guard через систему dependency injection.
Метод context.switchToHttp().getRequest() повертає HTTP-запит. Через нього guard отримує доступ до заголовків, параметрів і тіла запиту.
У реальному застосунку замість порівняння з рядком потрібно перевіряти токен через сервіс автентифікації. Однак принцип роботи guard залишається таким самим.
Guard підключається до маршруту за допомогою декоратора @UseGuards().
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('profile')
export class ProfileController {
@Get()
@UseGuards(AuthGuard)
getProfile() {
return {
message: 'Цей маршрут доступний лише автентифікованим користувачам',
};
}
}Тепер NestJS спочатку виконає AuthGuard, а вже потім — getProfile().
Запит без заголовка Authorization отримає помилку 401 Unauthorized:
GET /profileДля успішної автентифікації потрібно передати Bearer-токен:
GET /profile
Authorization: Bearer demo-tokenНижче наведено мінімальний застосунок, у якому guard захищає маршрут /profile.
import {
CanActivate,
Controller,
ExecutionContext,
Get,
Injectable,
Module,
NestFactory,
UnauthorizedException,
UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
type AuthenticatedRequest = Request & {
user?: {
id: number;
username: string;
};
};
@Injectable()
class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request =
context.switchToHttp().getRequest<AuthenticatedRequest>();
const authorization = request.headers.authorization;
if (typeof authorization !== 'string') {
throw new UnauthorizedException('Потрібен токен автентифікації');
}
const [scheme, token] = authorization.split(' ');
if (scheme !== 'Bearer' || token !== 'demo-token') {
throw new UnauthorizedException('Недійсний токен автентифікації');
}
// Додаємо автентифікованого користувача до запиту
request.user = {
id: 1,
username: 'alice',
};
return true;
}
}
@Controller('profile')
class ProfileController {
@Get()
@UseGuards(AuthGuard)
getProfile(request: AuthenticatedRequest) {
return {
message: 'Профіль користувача',
user: request.user,
};
}
}
@Controller('health')
class HealthController {
@Get()
getHealth() {
return {
status: 'ok',
};
}
}
@Module({
controllers: [ProfileController, HealthController],
})
class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();У цьому прикладі:
/health доступний без автентифікації;
/profile захищений AuthGuard;
guard додає дані користувача до об'єкта запиту;
контролер отримує ці дані через параметр request.
Перевірити маршрути можна такими запитами:
curl http://localhost:3000/healthcurl http://localhost:3000/profilecurl -H "Authorization: Bearer demo-token" \
http://localhost:3000/profileЯкщо всі маршрути контролера повинні бути захищені, @UseGuards() можна розмістити на рівні класу:
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('admin')
@UseGuards(AuthGuard)
export class AdminController {
@Get('dashboard')
getDashboard() {
return {
page: 'dashboard',
};
}
@Post('settings')
updateSettings() {
return {
updated: true,
};
}
}У такому випадку AuthGuard запускатиметься перед кожним методом AdminController.
Це зручно, коли всі маршрути контролера мають однакові вимоги до доступу.
Guard також можна застосувати до всіх маршрутів застосунку через APP_GUARD:
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
import { ProfileController } from './profile.controller';
@Module({
controllers: [ProfileController],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}Після цього AuthGuard запускатиметься для кожного маршруту.
Глобальний guard потрібно використовувати обережно: службові маршрути на кшталт перевірки стану застосунку також можуть потребувати окремого винятку з автентифікації. Для невеликих застосунків часто простіше підключати guard на рівні конкретного маршруту або контролера.
Після успішної перевірки guard може додати автентифікованого користувача до запиту:
request.user = {
id: 1,
username: 'alice',
};Обробник може використати ці дані:
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import { AuthGuard } from './auth.guard';
type AuthenticatedRequest = Request & {
user: {
id: number;
username: string;
};
};
@Controller('orders')
export class OrdersController {
@Get()
@UseGuards(AuthGuard)
getOrders(@Req() request: AuthenticatedRequest) {
return {
userId: request.user.id,
orders: [],
};
}
}Таким чином, guard не лише дозволяє або забороняє доступ, а й передає результат автентифікації далі в застосунок.
Перевірка токена часто потребує звернення до бази даних або зовнішнього сервісу. У такому разі canActivate() може бути асинхронним:
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
@Injectable()
export class AsyncAuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
if (!token) {
throw new UnauthorizedException();
}
const isValid = await this.validateToken(token);
if (!isValid) {
throw new UnauthorizedException('Недійсний токен');
}
return true;
}
private async validateToken(token: string): Promise<boolean> {
// Тут могла б бути асинхронна перевірка через сервіс автентифікації
return token === 'Bearer demo-token';
}
}NestJS дочекається результату Promise, перш ніж вирішити, чи викликати обробник маршруту.
Для HTTP-запиту NestJS виконує обробку приблизно в такому порядку:
middleware;
guards;
interceptors перед обробником;
pipes;
метод контролера;
interceptors після обробника;
filters у разі винятку.
Завдання guard — прийняти рішення про доступ до маршруту до виконання його основної логіки.
true без перевіркиGuard не повинен завжди повертати true:
canActivate(): boolean {
return true;
}Такий guard фактично нічого не захищає.
false для помилки автентифікаціїПовернення false зазвичай призводить до відповіді 403 Forbidden. Для відсутнього або недійсного токена коректніше викидати UnauthorizedException, щоб повернути 401 Unauthorized.
throw new UnauthorizedException('Потрібна автентифікація');401 означає, що користувач не пройшов автентифікацію. 403 зазвичай означає, що користувач відомий, але не має необхідних прав.
Перевірку автентифікації потрібно виконувати в guard, а не всередині кожного методу контролера. Це централізує логіку і не дозволяє випадково залишити маршрут без захисту.
Якщо guard застосований до контролера, усі його методи будуть захищені. Перевіряйте, чи не належать до цього контролера маршрути, які мають бути публічними.
До request.user варто додавати лише дані, потрібні для подальшої обробки. Не слід зберігати там пароль або повний секретний токен.
Guard перевіряє доступ до маршруту перед виконанням його обробника.
Для створення guard потрібно реалізувати CanActivate і метод canActivate().
true дозволяє виконання маршруту.
Для помилки автентифікації слід використовувати UnauthorizedException.
@UseGuards() можна застосувати до окремого маршруту або всього контролера.
Guard може додати автентифікованого користувача до об'єкта HTTP-запиту.
Для перевірок через базу даних або зовнішній сервіс canActivate() може бути асинхронним.