본문 바로가기
개발/Nest.js

02) Nest.js JWT인증 + RoleGuard 가이드

by jeounpar 2023. 6. 9.

전체 코드 : https://github.com/jeounpar/nestjs-jwt-roleguard-tutorial

1. role-guard.decorator.ts 파일 생성

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

@Role() 데코레이터로 사용된다.

 

2.auth.guard.ts 파일 수정

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

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private jwtService: JwtService, private reflector: Reflector) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);
    const handler = context.getHandler();
    const apiRoles: string[] = this.reflector.get<string[]>('roles', handler);

    if (!token) {
      throw new UnauthorizedException();
    }
    try {
      const payload = await this.jwtService.verifyAsync(token, {
        secret: process.env.JWT_SECRET,
      });
      if (!apiRoles.includes(payload.role)) {
        throw new UnauthorizedException();
      }
    } catch {
      throw new UnauthorizedException();
    }
    return true;
  }

  private extractTokenFromHeader(request: Request): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

Reflector

NestJS에서 사용되는 리플렉션(reflection)을 지원하는 유틸리티 클래스이다. 리플렉션은 실행 중인 코드의 구조, 타입 및 어노테이션과 같은 메타데이터에 대한 정보를 검사하고 조작할 수 있는 능력을 말한다. Reflector 클래스는 이러한 리플렉션 작업을 간단하게 수행할 수 있도록 도우며, 주로 NestJS의 가드(Guard), 인터셉터(Interceptor), 필터(Filter), 파이프(Pipe) 등에서 활용된다.

ex) 어노테이션 추출, 메타데이터 조회, 메서드/프로퍼티 파라미터 타입 조회 등등

 

3.auth.service.ts 파일 수정

...
  async login(id: string, password: string) {
    const payload = {
      id: id,
      password: password,
      role: 'user',
    };
    const accessToken = await this.jwtService.signAsync(payload);
    return accessToken;
  }
...

payload에 role을 부여한다. (모든 클라이언트는 user 역할을 부여 받는다)

 

4. app.controller.ts 및 app.service.ts 파일 수정

// app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from './auth/auth.guard';
import { Roles } from './decorator/role-guard.decorator';

@UseGuards(AuthGuard)
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Roles('user')
  @Get('user')
  getUser(): string {
    return this.appService.getUser();
  }

  @Roles('admin')
  @Get('admin')
  getAdmin(): string {
    return this.appService.getAdmin();
  }
}
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getUser(): string {
    return 'User!';
  }
  getAdmin(): string {
    return 'Admin!';
  }
}

GET /user API는 Role이 user만 호출 가능

GET /admin API는 Role이 admin만 호출 가능

 

4. RoleGuard 테스트

user가 GET /user API 호출
user가 GET /admin API 호출

Role은 enum 클래스를 선언해서 사용하는 것이 좋아보인다.

요걸 잘 활용하면 OAuth 인증에도 사용 가능!