이번 글에서는 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 기능은 아니기때문에 따로 코드를 만들어줘야합니다.
AuthGuard 구성하기
//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하는 로직을 짤것입니다.
컨트롤러에 Guards 바인딩하기
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {}
위와 같이 @nestjs/common 에서 제공하는 UseGuard 데코레이션을 사용하여 위에서 만든 AuthGuard를 넣어줍니다.
App 모듈에서 GUARD 를 위한 설정해주기
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
Roles 데코레이션 만들어주기
// 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를 리턴하는 로직을 만들어줄것입니다.
AuthGuard에 추가 로직 구현하기
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 처리를 하였습니다.