이번 글에서는 CanActivate를 implement받아 authorization 를 가드하는 방법을 알아보겠습니다.
예를들어 아래와 같이 @Roles(’admin’)을 사용함으로써 관리자가 아닌 일반 유저가 저 api에 요청을 했을때 exception 처리를 해보려고합니다.
// * Roles 데코레이션을 사용함으로써 해당 엔드포인트의 auth guard를 유연하게 가능합니다. @Post() @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); }
하지만 Roles는 nest.js에서 자체적으로 제공해주는 decorator 기능은 아니기때문에 따로 코드를 만들어줘야합니다.
//src/guards/auth.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { return true } }
위 코드에서 request을 받고 headers에 있는 authorization 정보를 받아 Roles의 decoration 인자와 일치하는지 비교하여 일치하면 true를 리턴하고 일치하지 않으면 false를 return하는 로직을 짤것입니다.
@Controller('cats') @UseGuards(AuthGuard) export class CatsController {}
위와 같이 @nestjs/common 에서 제공하는 UseGuard 데코레이션을 사용하여 위에서 만든 AuthGuard를 넣어줍니다.
import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; @Module({ providers: [ { provide: APP_GUARD, useClass: AuthGuard, }, ], }) export class AppModule {}
// src/decorators/roles.decoration.ts import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
SetMetadata를 사용하여 커스텀 메타데이터를 route handler에 roles라를 메타데이터 이름으로 roles를 설정 할 수 있습니다. roles에 rest파라미터를 사용하여 복수개의 인자들을 받아 사용이 가능합니다.
해당 코드에서 설정한 roles 메타데이터를 가장 위에서 만들었던 AuthGuard 클래스에서 get을 해서 분기처리를 하여 true나 false를 리턴하는 로직을 만들어줄것입니다.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const roles = this.reflector.getAllAndOverride('roles', [ context.getHandler(), context.getClass(), ]); if (roles.length) { const request = context.switchToHttp().getRequest(); const token = request.headers?.authorization?.split('Bearer ')[1]; try { const payload = (await jwt.verify( token, process.env.JSON_TOKEN_KEY, )) as JWTPayload; const user = await this.prismaService.user.findUnique({ where: { id: payload.id, }, }); if (!user) return false; if (roles.includes(user.user_type)) return true; return false; } catch (error) { return false; } } return true; } }
Reflector를 사용하여 roles에서 했던 커스텀 Metadata를 가져올 수 있습니다.
만약 Controller에서 @Roles(’admin’,’adim2’)를 사용했다면 roles에는 [’admin’,’admin2’]가 들어오게 될 것입니다. 즉 Roles 데코레이션에 인자가 들어와 데코레이션이 적용되었다면 첫번째로 보이는 조건문을 타게될것이고, 그 안에서 자신만의 프로젝트에 알맞는 로직을 구성하면 될것입니다.
저 같은 경우는 prisma orm을 사용하여 db에 접근하여 해당 유저테이블에서 가지고 있는 user_type과 데코레이션 인자를 비교해서 auth 처리를 하였습니다.