개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

NestJS Guard — 인증과 인가를 처리하는 실행 가드

2025년 3월 10일
NestJSGuardCanActivateJWTAuthorization

NestJS에서 요청이 Controller에 도달하기 전, 해당 요청을 허용할지 말지를 결정하는 계층이 Guard다. Middleware가 HTTP 맥락에만 의존하는 반면, Guard는 ExecutionContext를 통해 현재 실행 컨텍스트 전체에 접근할 수 있어 훨씬 정교한 인가 로직을 작성할 수 있다. 인증 토큰 검증부터 역할 기반 접근 제어까지, Guard 하나로 선언적으로 처리할 수 있다.

요청 처리 흐름에서의 위치

Client Request
     │
     ▼
 Middleware
     │
     ▼
  Guard  ◄── 여기서 허용/거부 결정
     │
     ▼
 Interceptor (pre)
     │
     ▼
  Pipe
     │
     ▼
 Controller

Guard가 false를 반환하거나 예외를 던지면 이후 파이프라인은 실행되지 않는다.

CanActivate 인터페이스

모든 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 등으로 프로토콜별 컨텍스트로 전환한다.

JWT Guard 예시

실무에서 가장 자주 쓰이는 패턴은 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 — 역할 기반 인가

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);

@UseGuards 적용

Guard는 메서드, 컨트롤러, 전역 세 수준에서 적용할 수 있다.

// 메서드 단위
@Get('profile')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
getProfile() { ... }

// 전역 등록 (main.ts)
app.useGlobalGuards(new JwtAuthGuard(jwtService));

Middleware와의 차이

항목MiddlewareGuard
실행 시점Guard 이전Pipe 이전
컨텍스트 접근HTTP만HTTP / WS / RPC
반환값으로 제어next 호출 여부boolean 반환
DI 사용제한적완전 지원
권장 용도로깅, CORS, 쿠키 파싱인증, 인가

Middleware는 next를 호출해 파이프라인을 계속 진행시키지만, Guard는 반환값만으로 허용·거부를 결정한다. 인가 로직은 반드시 Guard에 두는 것이 NestJS의 관례다.

← 이전 글NestJS Middleware — Controller 이전에 실행되는 함수
다음 글 →NestJS Pipe — 입력 데이터의 검증과 변환을 담당하는 파이프