JavaScript

NestJS Custom Decorator로 유저 인증 처리하기

MIRACLE LIFE 2025. 2. 19. 22:26

Nest는 TypeScript의 데코레이터를 기반으로 한 Custom 데코레이터를 더 쉽게 사용할 수 있도록 기능을 제공한다.

이를 활용하면 비즈니스 로직과 관련 없는 부분을 숨기고, 공통 처리를 통해 중복 코드를 줄이며 재사용성을 높일 수 있다.

 

이번에는 @User() Custom 데코레이터를 생성하여, 필요한 컨트롤러에서 유저 정보를 간편하게 가져올 수 있도록 하겠다.

 


 

먼저, Custom decorator에 대한 이해를 돕기 위한 간단한 예제이다.

이 예제는 Nest 공식 문서에서 확인할 수 있다.
https://docs.nestjs.com/custom-decorators

@Get()
getProfile(@Req() req) {
	const user = req.user;
}

기존에는 사용자 정보를 필요로 하는 각 컨트롤러에서 매번 요청 객체(request)에서 데이터를 수동으로 추출해야 했다.

 

 

// user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

 

@Get()
getProfile(@User() user) {
	console.log(user);
}

 

createParamDecorator를 이용해 @User() Custom 데코레이터를 만들어서 간편하게 사용할 수 있다.

 


 

이번 글에서는 요청 객체(Request)에 user 데이터를 첨부하는 방법과 함께, 유저 인증 과정도 다룰 예정이다.

 

이를 통해 '이미 검증된 온전한 User 데이터'가 필요한 곳에서 @User() 데코레이터만 사용하면 되도록 만드는 것이 목표다.

예제에서는 인증을 위해서 JWT 토큰을 사용한다.


 

방법1. 가드(Guard)와 함께 사용

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';
import { JwtService } from '@nestjs/jwt';

import { UserService } from '../user/user.service';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(
    private readonly jwtService: JwtService,
    private readonly userService: UserService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request: Request = context.switchToHttp().getRequest();
    const authHeader = request.headers.authorization;
    if (!authHeader) return false;

    const token = authHeader.split(' ')[1];
    if (!token) return false;

    try {
      const decoded = this.jwtService.verify(token, { secret: process.env.JWT_SECRET });
      const user = await this.userService.findOne(decoded.userId);
      if (!user) return false;

      request.user = user; // 유저 정보를 요청 객체에 저장
      return true;
    } catch (error) {
      return false;
    }
  }
}

 

@UseGuards(JwtAuthGuard) // 가드 적용
@Get()
getProfile(@User() user) {
	console.log(user);
}

 

이 방법에는 한 가지 문제점이 있다.

@User() 데코레이터를 기대한 대로 사용하려면 반드시 앞서 정의한 가드와 함께 적용해야 한다. 그러나 이 과정에서 가드 적용 코드를 빠트리는 실수(휴먼 에러) 가 발생할 가능성이 있다.

 

 


 

방법2. @USER() 데코레이터 단독 사용

이 방법은 Custom 데코레이터를 생성하는 과정에서 유저 정보를 함께 저장하는 방식이다.

하지만 여기에는 한 가지 문제가 있다. 데코레이터 내부에서는 Provider에 직접 접근할 수 없으므로, 다음과 같은 일반적인 코드로는 유저 데이터를 가져올 수 없다.

const user = await this.userService.findOne(userId);

 

이 때, 사용할 수 있는 방법으로는 Provider를 글로벌 변수로 저장하고 필요한 곳에서 사용하는 것이다.

 

1. 글로벌 변수 ProviderRegistry 생성

class ProviderRegistry {
  private static providers = new Map<string, any>();

  static set(name: string, provider: any) {
    this.providers.set(name, provider);
  }

  static get(name: string) {
    return this.providers.get(name);
  }
}

set()으로 Provider를 저장하고 get()으로 가져올 수 있다.

 

2. Provider를 글로벌 변수에 저장

import { Module, OnModuleInit } from '@nestjs/common';
import { UserService } from './user.service';

export class AppModule implements OnModuleInit {
  constructor(private readonly userService: UserService) {}

  onModuleInit() {
    ProviderRegistry.set('UserService', this.userService);
  }
}

OnModuleInit() 에서 사용할 Provider를 ProviderRegistry에 등록한다.

 

3. 데코리에터에서 Provider 사용

// user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  async (data: unknown, ctx: ExecutionContext) => {
    ... 
    const userService: UserService = ProviderRegistry.get('UserService');
    const user = await userService.findOne(decoded.userId);
    return user;
  },
);

 

@Get()
getProfile(@User() user) {
	console.log(user);
}

이제 원하는 대로 @User() 데코레이터 하나만 사용하여 유저 정보를 가져올 수 있게 되었다.

 

다만, 이 방식에는 Nest가 ProviderRegistry를 추적하지 못한다는 점이 있지만, 이 점이 큰 문제는 아닐 수 있다.

물론, 프로바이더를 글로벌 변수로 등록하는 추가 작업이 필요하지만, 인증된 유저 데이터가 필요할 때 @User() 데코레이터 하나만 사용하면 된다는 점에서 괜찮은 방법이라고 생각한다.

 


마무리

Nest에서 제공하는 createParamDecorator 뿐만 아니라, Reflector.createDecorator를 사용하여 원하는 데코레이터를 만들 수 있다.

이를 통해 중복 코드를 줄이고, 공통 관심사를 처리하여 코드를 모듈 단위로 효율적으로 관리할 수 있다.

 

 

https://docs.nestjs.com/fundamentals/execution-context#reflection-and-metadata

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

https://www.typescriptlang.org/ko/docs/handbook/decorators.html

 

Documentation - Decorators

TypeScript Decorators overview

www.typescriptlang.org