리팩토링 전 코드


private extractToken(headers: { authorization?: string }): string {
    const token = headers.authorization;
    if (!token) {
      throw new UnauthorizedException('유저 정보가 없습니다.');
    }
    return token;
  }

@Get()
async getStore(@Headers() headers?: { authorization?: string | undefined }) {
  const token = this.extractToken(headers);

  return await this.storeService.getUserStores(token);
}

controller내부에extreactToken이라는 함수를 통해 header의 값을 받고 token을 추출해내는 과정이 필요했다.

하지만 위의 과정은 비효율적으로 반복되는 코드를 작성해야해서 재사용성이 떨어지며 코드의 추상화가 안되는 문제를 초래했다. 이 문제는 AuthGuard 개념을 이용해 손 쉽게 리팩토링 할 수 있었다.

리팩토링 후 코드


jwt.strategy.ts

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { UsersRepository } from 'src/modules/users/users.repository';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly usersRepository: UsersRepository) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Header의 token에서 추출(HTTP 헤더의 Authorization 부분에서 Bearer 토큰을 추출)
      secretOrKey: process.env.JWT_SECRET, // key, 유출되면 안됨, AuthModule의 JwtModule.register의 secret와 맞춰줘야한다.
      igonoreExpiration: false, // 만료되는 기간
    }); // jwt에 대한 설정(jwt인증 매커니즘을 설정하는 역할)
  }

  async validate(payload: any) {
    // payload는 JWT에서 디코딩된 정보 예: { sub: userId, email: ... }
    // 여기서 DB 조회 등을 통해 사용자 확인 가능.
    return { userId: payload.sub, ...payload };
  }
}

validate() 메서드 자체가 직접 request.user에 값을 주입하는 코드를 쓰는 것은 아니다. 대신, passport 및 @nestjs/passport 패키지의 동작 방식에 의해 validate() 메서드가 반환한 객체가 request.user로 설정한다.

즉, validate 메서드의 반환값을 passport 측에서 받아서 최종적으로 request.user에 할당하게 된다.

jwt.guard.ts

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

// Guard는 Nest.js에서 라우트 핸들러의 실행 전에 특정 조건을 충족하는지 검증하는 역할
// JWT Guard는 주로 사용자가 유효한 JWT를 제공했는지 확인하는 데 사용

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  // JwtGuard는 AuthGuard를 확장한다. AuthGuard는 Passport를 기반으로 작동하며, 특정 Strategy를 사용하여 인증을 처리한다. 이 경우 passport-jwt Strategy를 사용
  // Guard는 요청을 받아 토큰의 유효성을 검사한다. 유효한 토큰이 있는 경우 요청을 계속 진행시키고, 유효하지 않은 경우 에러를 반환
  // JwtGuard는 내부적으로 JwtStrategy에서 정의된 토큰 추출 및 검증 로직을 사용한다. 이는 주로 JwtStrategy가 passport-jwt의 옵션을 통해 정의된 방법을 따른다.

  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }
}

super.canActivate(context)를 호출하면 AuthGuard('jwt') 클래스 내부의 canActivate 메서드가 실행된다.

AuthGuard('jwt') 클래스의 canActivatepassport.authenticate('jwt', ...)를 호출하여 Passport 전략(jwt.strategy)을 통해 JWT 토큰을 검증한다.

토큰 검증에 성공하면 validate() 메서드가 호출되어 사용자 정보를 request.user에 주입한 뒤 true를 반환한다. 검증 실패 시 예외를 던져 요청이 Controller에 도달하기 전에 막히게 된다.