LoginSignup
19
7

More than 3 years have passed since last update.

NestJSでGraphQLをGuardする

Posted at

NestJSでGraphQLのQueryまたはMutationをGuardする方法を説明します。
基本的には、NestJSの公式に掲載されていますが、GraphQLをGuardするには少し工夫が必要です。

環境

  • NestJS 6.0

ゴール

NestJSでGraphQLをGuardする方法はいくつかあります。
今回は、ユーザのロールによってGuardする方法を採用します。
以下のような感じでGaurdします。@nestjs/passportのjwtでGuardした上で、ユーザのロールが2となっているもののみアクセスできるmutationを実装します(queryでも同じです)

sample.ts
    @Mutation(returns => SaveSiteConfigResult)
    @UseGuards(JwtAuthGuard)
    @Roles(2)
    savesiteconfig(@Args('savesiteconfig') _saveSiteConfig: SaveSiteConfig): Promise<SaveSiteConfigResult> {

Gaurdの実装

まずは、Guardの実装です。
詳しくはコメントに記載しましたが、ざっくりとは以下の通りです。

  • SetMetadataでGuard定義されているロール値を取得する
  • ロール値が取得できない場合は、Guard定義されていないと言うことになるので、trueを返す
  • authorizationヘッダーからJWTトークンを取得を取得し公開鍵で検証する
  • JWTトークンのpayloadに設定されているロール値がGuard定義されているロール値に該当するかをチェックする
rolesguard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GqlExecutionContext } from '@nestjs/graphql';
import * as jwt from 'jsonwebtoken';
import { IJwtPayload } from '../auth/interfaces/ijwt-payload.interface';

@Injectable()
export class RolesGuard implements CanActivate {
    constructor(
        private readonly reflector: Reflector
    ) {}

    canActivate(context: ExecutionContext): Promise<boolean> {
        return new Promise((resolve, reject) => {
            // SetMetadataのrolesに定義されたロール値を取得する
            const roles = this.reflector.get<number[]>('roles', context.getHandler());
            // ロール値が取得できなければtrue(つまり、ガードされていない)
            if (!roles) {
                resolve(true);
            }
            else {
                // contextからGraphQLのctxを取得する
                // GraphQLのforRootで「context: ({ req }) => ({ req }),」の定義が必要
                const ctx = GqlExecutionContext.create(context);
                const req = ctx.getContext().req;
                // authorizationヘッダーからJWTトークンを取得する
                const authToken = req.headers.authorization;
                const jwtToken = authToken.substring(authToken.indexOf(' ') + 1);
                // JWTトークンを公開鍵で検証する
                jwt.verify(jwtToken, Buffer.from(process.env.RSA_PUBLIC_KEY, 'base64'), (err, payload: IJwtPayload) => {
                    // JWTトークンの検証ができなかったらエラー
                    if (err) {
                        reject(err);
                    }
                    else {
                        // ユーザのロールがGuardで設定されているロールに合致するかを検証する
                        resolve(roles.includes(payload.role));
                    }
                });
            }
        });
    }
}

GraphQLを使う時のポイントは、上記のコードの以下の部分です(これ以外は公式に記載のもので実現可能です)

                // contextからGraphQLのctxを取得する
                // GraphQLのforRootで「context: ({ req }) => ({ req }),」の定義が必要
                const ctx = GqlExecutionContext.create(context);
                const req = ctx.getContext().req;

Decoratorの定義

上のものでGuardするには、@SetMetadata('roles', 1)とする必要があります。
これでもいいのですが、もう少し分かりやすくするように、Decoratorを定義します。これで、@Roles(1)と書くことができます。

roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

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

グローバルなGuardとして定義

app.modules.tsで作成したGuardをグローバルGuardとして定義します。
これで、全てのGraphQLで作成したGuardがコールされるようになります。

app.modules.ts
import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './guards/rolesguard';

@Module({
  providers: [
    { provide: APP_GUARD, useClass: RolesGuard }
  ],
})
19
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
7