Что такое guards и как реализовать ролевую авторизацию в NestJS?

MiddleNestJS · Backend·Обновлено 30 августа 2026
Коротко
Guard — это класс, реализующий интерфейс CanActivate, который решает, обрабатывать ли запрос дальше, на основе контекста выполнения (например, ролей пользователя). Ролевую авторизацию делают через кастомный декоратор @Roles(), метаданные Reflector и RolesGuard, который сравнивает роли пользователя из request с требуемыми ролями хендлера.

Что такое Guard

Guard в NestJS — это класс с методом canActivate(), который возвращает boolean (или Promise<boolean> / Observable<boolean>). Он определяет, будет ли запрос передан обработчику маршрута, или его нужно отклонить. Guard'ы выполняются после middleware, но до pipes и до самого хендлера — это делает их идеальным местом для проверки авторизации (аутентификация обычно уже сделана раньше, например в middleware или предыдущем guard).

Oтличие от middleware в том, что guard имеет доступ к ExecutionContext — обёртке над запросом, которая знает, какой класс и метод контроллера будет вызван, что позволяет читать метаданные, навешанные декораторами.

Механизм: ExecutionContext и Reflector

Для ролевой авторизации нужны два элемента:

  1. Кастомный декоратор @Roles(), который сохраняет список разрешённых ролей как метаданные метода/класса через SetMetadata.
  2. RolesGuard, который через Reflector читает эти метаданные и сравнивает их с ролями текущего пользователя (обычно они попадают в request.user после AuthGuard / JWT-стратегии).

Реализация

Сначала декоратор:

// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

Затем guard:

// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // получаем требуемые роли с метода и класса
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true; // роли не заданы — доступ разрешён
    }

    const { user } = context.switchToHttp().getRequest();
    // предполагается, что user.roles заполнен предыдущим AuthGuard
    return requiredRoles.some((role) => user?.roles?.includes(role));
  }
}

Применение на контроллере:

// users.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard } from './roles.guard';
import { Roles } from './roles.decorator';

@Controller('admin')
@UseGuards(AuthGuard('jwt'), RolesGuard) // порядок важен: сначала auth, потом roles
export class AdminController {
  @Get('users')
  @Roles('admin')
  getUsers() {
    return 'только для админов';
  }
}

Глобальная регистрация

Guard можно применить локально (@UseGuards), на уровне контроллера, или глобально через APP_GUARD в модуле — тогда он будет выполняться для всех маршрутов, а точечные исключения делаются через собственные декораторы (например @Public() с проверкой в guard'е).

Важные нюансы

  • Guard'ы выполняются в порядке, в котором указаны в @UseGuards, и для всех уровней (глобальный → контроллер → метод), поэтому аутентификация должна стоять раньше проверки ролей.
  • getAllAndOverride берёт метаданные метода, а если их нет — метаданные класса, что удобно для дефолтных ролей на уровне контроллера.
  • Guard не должен выполнять бизнес-логику — только принимать решение true/false, при отказе NestJS сам бросает ForbiddenException (403).

Что хочет услышать интервьюер

Понимание, что Guard реализует CanActivate и работает через ExecutionContext

Знание места guard'ов в жизненном цикле запроса (после middleware, до pipes/interceptors)

Умение объяснить связку кастомный декоратор + SetMetadata + Reflector

Понимание, что аутентификация и авторизация — разные guard'ы, и важен их порядок

Знание способов регистрации: локально, на контроллере, глобально через APP_GUARD

Пример: roles.decorator.ts

import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

Пример: roles.guard.ts

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user?.roles?.includes(role));
  }
}

Пример: admin.controller.ts

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard } from './roles.guard';
import { Roles } from './roles.decorator';

@Controller('admin')
@UseGuards(AuthGuard('jwt'), RolesGuard)
export class AdminController {
  @Get('users')
  @Roles('admin')
  getUsers() {
    return 'только для админов';
  }
}

Типичные ошибки

Путают guard с middleware и не могут объяснить разницу в доступных возможностях (ExecutionContext vs req/res/next)

Забывают, что Reflector.getAllAndOverride нужен для чтения метаданных, и пытаются получить роли напрямую из request

Не учитывают порядок guard'ов в @UseGuards, из-за чего RolesGuard выполняется раньше AuthGuard и user ещё не определён

Реализуют проверку ролей внутри самого контроллера или сервиса вместо выделения в переиспользуемый guard

Не обрабатывают случай отсутствия метаданных ролей (undefined), из-за чего маршруты без @Roles() случайно блокируются

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее