NestJS에서 요청이 Controller에 도달하기 전, 해당 요청을 허용할지 말지를 결정하는 계층이 Guard다. Middleware가 HTTP 맥락에만 의존하는 반면, Guard는 ExecutionContext를 통해 현재 실행 컨텍스트 전체에 접근할 수 있어 훨씬 정교한 인가 로직을 작성할 수 있다. 인증 토큰 검증부터 역할 기반 접근 제어까지, Guard 하나로 선언적으로 처리할 수 있다.
Client Request
│
▼
Middleware
│
▼
Guard ◄── 여기서 허용/거부 결정
│
▼
Interceptor (pre)
│
▼
Pipe
│
▼
Controller
Guard가 false를 반환하거나 예외를 던지면 이후 파이프라인은 실행되지 않는다.
모든 Guard는 CanActivate 인터페이스를 구현해야 한다. canActivate 메서드는 boolean 또는 Promise<boolean>, Observable<boolean>을 반환한다.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return Boolean(request.headers['authorization']);
}
}
ExecutionContext — HTTP, WebSocket, RPC 등 다양한 프로토콜의 실행 컨텍스트를 추상화한 객체. switchToHttp, switchToWs 등으로 프로토콜별 컨텍스트로 전환한다.
실무에서 가장 자주 쓰이는 패턴은 Authorization 헤더의 JWT 토큰을 검증하는 Guard다.
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization']?.split(' ')[1];
if (!token) throw new UnauthorizedException();
try {
const payload = this.jwtService.verify(token);
request.user = payload;
return true;
} catch {
throw new UnauthorizedException();
}
}
}
검증된 페이로드를 request.user에 주입하면, 이후 Controller에서 @Req 또는 커스텀 데코레이터로 꺼내 쓸 수 있다.
Role Guard는 JWT Guard와 조합해 특정 역할을 가진 사용자만 라우트에 접근하도록 제한한다. Reflector로 메타데이터를 읽는 것이 핵심이다.
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user?.roles?.includes(role));
}
}
커스텀 데코레이터로 메타데이터를 설정한다.
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
Guard는 메서드, 컨트롤러, 전역 세 수준에서 적용할 수 있다.
// 메서드 단위
@Get('profile')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
getProfile() { ... }
// 전역 등록 (main.ts)
app.useGlobalGuards(new JwtAuthGuard(jwtService));
| 항목 | Middleware | Guard |
|---|---|---|
| 실행 시점 | Guard 이전 | Pipe 이전 |
| 컨텍스트 접근 | HTTP만 | HTTP / WS / RPC |
| 반환값으로 제어 | next 호출 여부 | boolean 반환 |
| DI 사용 | 제한적 | 완전 지원 |
| 권장 용도 | 로깅, CORS, 쿠키 파싱 | 인증, 인가 |
Middleware는 next를 호출해 파이프라인을 계속 진행시키지만, Guard는 반환값만으로 허용·거부를 결정한다. 인가 로직은 반드시 Guard에 두는 것이 NestJS의 관례다.