# guard

Nsetjs에는 가드 통신을 block 할 수 있는 interface가 존재한다. 바로 CanActive interface이다.

guard를 통해서 기존 node서버의 경우 Express에서 인증을 처리하던 미들웨어를 interface로 관리 할 수 있도록 해주는 기능이 탑재되어 있다.

미들웨어와의 차이점은 next() 함수를 호출한 후 어떤 행들러가 실행될지 모르지만,

guard의 경우 ExecutionContext 인스턴스에 엑세스할 수 있으므로 다음에 실행될 항목을 정확히 알고 있다

guard에서는 기본적으로 단일 책임을 원칙으로 합니다.

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> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

nestjs에서 제공하는 기본적인 Guard의 형식이다.

validateRequest에 해당하는 로직을 자체적으로 구성함으로서 Return되는 Boolean의 True/False 값에의하여 권한을 설정할 수 있다.

기본적으로 Guard의 경우에는 false에 경우에는 403 Forbidden Error를 내보낸다.

현재 개발중인 서버에서는 각 유저에게 권한을 부여하고 해당 권한을 통해 Graphql 통신을 block/allow 할 수 있는 guar가 필요하였다.

# custom-guard

현재 구성중인 서버에서 Graphql을 사용하고 유저의 인증은 JWT 토큰을 Passport를 통해 검증하도록 전략을 구성하였다. 위의 부분의 자세한 구현은 생략하고 가드의 자세한 내용을 바로 살펴보자.

import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class GqlRoleGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
  ) {}

  async canActivate(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    const policyList = this.reflector.get<PolicyList[]>(
      'policyList',
      context.getHandler(),
    );
    const findUser = ctx.getContext().req.user as User;
    ...
    return checkPolicies(findUser.role, allowPolicies, roles, policyList);
  }
}

중간 중간 자세한 구현 내용은 생략한다.

CanActivate interface에 대한 구현을 위해 implements에 선언하고 common 모듈에서 가져온다.

export class GqlRoleGuard implements CanActivate {}

이렇게 되면 abstract로 선언되어 있는 canActivate를 구현하여한다고 알려주고,

이를 원하는 요구사항에 맞게 구현하면된다.

import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GqlExecutionContext } from '@nestjs/graphql';

// interface
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>;

// 실제 구현
async canActivate(context: ExecutionContext) {
  const ctx = GqlExecutionContext.create(context);
  const roles = this.reflector.get<string[]>('roles', context.getHandler());
  const policyList = this.reflector.get<PolicyList[]>(
    'policyList',
    context.getHandler(),
  );
  const findUser = ctx.getContext().req.user as User;
  ...
  return checkPolicies(findUser.role, allowPolicies, roles, policyList);
}

nestjs의 intercepto나 guard 즉 미들웨어의 기능을 담당하는 것들에서는 javascript에서의 동작을 담당하는 ExecutionContext에 직접 접근하여 현재 서버의 실행컨텍스트를 직접 접근이 가능하다.

이러한 이점을 바탕으로 nestjs에서는 Graphql이 구동 되는 환경(ExcutionContext)에 접근하여이를 사용할 수 있다.

reflector는 nestjs에서 제공하는 SetMetadata를 통해 특정 메타데이터를 부여할 수 있고, 이러한 메타데이터를 엑세스할 수 있도록 돕는다.

GqlExcutionContext에서 현재 Context의 request에 바인딩된(passport를 통해) User정보를 가져온다.

해당 User 의 Role이 이 사용자의 역활을 의미하고.

permission은 Cache에서 수집하도록 구성하였다.

© Devlog from jeong