Skip to Content

Пример с AuthGuard и OnlyMeGuard

Возьмём пример из feeldown-backend (ссылка вне зоны доступа).

В проекте Feeldown аутентификация и авторизация разделены на два независимых Guard-а: AuthGuard и OnlyMeGuard. Это отличный пример Single Responsibility Principle (SRP) в действии.


AuthGuard – отвечает только за аутентификацию

@Injectable() export class AuthGuard implements CanActivate { public constructor( private readonly service: AuthGuardService, private readonly metadataHandler: MetadataHandler, private readonly publicHandler: PublicHandler, private readonly skipAuthGuardHandler: SkipAuthGuardHandler, ) {} public async canActivate(context: ExecutionContext): Promise<boolean> { const metadataValidated = await this.validateMetadata(context); if (metadataValidated) { return true; } const request = context.switchToHttp().getRequest<Request>(); const validate = () => this.service.validateRequest(request); return tryCatchThrow(validate); } private async validateMetadata(context: ExecutionContext) { return this.metadataHandler .apply(this.publicHandler, this.skipAuthGuardHandler) .execute(context); } }

Что хорошо:

  • AuthGuard не знает, как именно проверяется токен – это делегировано AuthGuardService.
  • Для обработки метаданных используется цепочка обработчиков (MetadataHandler, PublicHandler, SkipAuthGuardHandler), что делает код гибким и открытым для расширения (OCP).
  • Все зависимости внедряются через конструктор – нет нарушений Dependency Injection.
  • У класса одна причина для изменения: изменение логики аутентификации (например, переход с JWT на сессии).

OnlyMeGuard – отвечает только за проверку владельца ресурса

@Injectable() export class OnlyMeGuard implements CanActivate { public constructor( private readonly reflector: Reflector, private readonly service: OnlyMeGuardService, ) {} public async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest<Request>(); const handler = context.getHandler(); const metadata = this.reflector.get<OnlyMeMetadata>( Metadata.isOnlyMe, handler, ); if (!metadata) { return true; } const validated = await this.service.execute(metadata, request); return validated; } }

Что хорошо:

  • OnlyMeGuard только проверяет наличие метаданных @OnlyMe() и делегирует основную логику сервису OnlyMeGuardService.
  • Он не занимается парсингом токенов, работой с БД или трансформацией параметров – всё это вынесено.
  • Его единственная ответственность – решить, пропускать запрос или нет, основываясь на метаданных и результате сервиса.
  • Изменение правил проверки владельца затрагивает только OnlyMeGuardService, а сам Guard остаётся нетронутым.

Разделение аутентификации и авторизации

Вместо одного «толстого» Guard-а, который делал всё, мы получили два узкоспециализированных класса:

GuardОтветственность
AuthGuardПроверить, что пользователь аутентифицирован (токен валиден)
OnlyMeGuardПроверить, что текущий пользователь является владельцем ресурса

Это позволяет гибко комбинировать их в контроллерах:

@UseGuards(AuthGuard, OnlyMeGuard) @Delete(ROUTES.DELETE) public delete(@Param("id") id: string) { // ... }

Что мы получаем

  • Чёткое разделение ответственности – каждый Guard делает ровно одно дело.
  • Лёгкость тестирования – каждый Guard и его сервис тестируются изолированно.
  • Гибкость – можно легко добавить новый Guard (например, RolesGuard) без изменения существующих.
  • Соблюдение DIP – зависимости внедряются, а не создаются внутри.

Вывод: AuthGuard и OnlyMeGuard – это эталонный пример SRP в реальном проекте.
Они показывают, как грамотно разбить сложную логику на маленькие, легко поддерживаемые куски.

Last updated on