Nest.jsでGuardからControllerへデータを渡す方法
Nest.jsでは公式ドキュメントにも書いてあるとおり、Guardを作成する際には
-
CanActivate
インターフェースを継承する -
canActivate
メソッド中でbooleanを返却する
という実装をすることで対象のリクエストの呼び出し可否をコントロールするというのが基本的な使い方です。
しかし、実務で色々なGuardを実装していると、Guardで取得したデータをControllerでも使いたくなるケースに時々出くわします。
GuardとControllerとで同じデータを取得する処理を重複して実装するのは無駄なので、今回はこのGuard内で取得したデータをControllerへと渡す実装をしてみようと思います。
1. Guardのベースとなる抽象化クラス「BaseGuard」を実装する
公式ドキュメントのPassportの実装を参考にします。
認証用モジュールである@nestjs/passport
を利用する際には、Guard内のhandleRequest
メソッドの中で任意の検証をしつつ、ユーザー情報を返却するような実装をする必要があります。
例えば、公式ドキュメントにあるjwtの認証を行うGuardでは、下記の様な実装をしています。
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
// Add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
このJwtAuthGuard
を対象のControllerにセットすると、requestからuserの情報が取得できる様になります。
このuserの値が、先ほどJwtAuthGuard
のhandleRequest
メソッドで返却していたユーザーの情報になります。
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
@Controller()
export class AppController {
@UseGuards(JwtAuthGuard)
@Post('auth/login')
async login(@Request() req) {
return req.user;
}
}
これと似たような実装をすべくAuthGuard
の実装を参考にしてみます。
AuthGuard
では、canActivateに以下のような実装をしています。
https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts
async canActivate(context: ExecutionContext): Promise<boolean> {
const options = {
...defaultOptions,
...this.options,
...await this.getAuthenticateOptions(context)
};
const [request, response] = [
this.getRequest(context),
this.getResponse(context)
];
const passportFn = createPassportContext(request, response);
const user = await passportFn(
type || this.options.defaultStrategy,
options,
(err, user, info, status) =>
this.handleRequest(err, user, info, context, status)
);
request[options.property || defaultOptions.property] = user;
return true;
}
これはつまり、handleRequest
をcanActivate
内で呼び出し、handleRequest
の戻り値をrequestに設定しているだけということです。
この実装を踏まえ、下記のようなベースとなる抽象化クラスを実装します。
import { CanActivate, Injectable, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export abstract class BaseGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const data = await this.handleRequest(context);
const request = context.switchToHttp().getRequest();
request['guard'] = data;
return !!data;
}
abstract handleRequest(context: ExecutionContext): any;
}
BaseGuard
では抽象メソッドhandleRequest
を用意し、その戻り値をcanActivate
メソッド内でguard
という名称でrequestに格納しています。
2. Guardからのデータを取得するデコレーターを実装する。
BaseGuard
のhandleRequest
メソッドでrequestに格納された値を取得するデコレーターを作成します。
request['guard']
からデータを取得するだけなので、下記の様な実装になります。
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const GuardResponse = createParamDecorator((property: string, context: ExecutionContext) => {
const request = context.switchToHttp().getRequest();
const data = request['guard'];
return property ? data?.[property] : data;
});
3. BaseGuardを継承したGuardを実装する
サンプルとして、Httpヘッダーにdemo-id
というデータが存在するかチェックをするGuardを作成します。
import { Injectable, ExecutionContext } from '@nestjs/common';
import { HttpException, HttpStatus } from '@nestjs/common';
import { Demo, DemoDocument } from '../schemas/demo.schema';
import { BaseGuard } from './base.guard';
@Injectable()
export class DemoIdGuard extends BaseGuard {
constructor(
@InjectModel(Demo.name)
private readonly demoModel: Model<DemoDocument>,
) {
super();
}
async handleRequest(context: ExecutionContext): Promise<Demo> {
const { headers } = context.switchToHttp().getRequest();
const { 'demo-id': demoId } = headers;
if (!demoId) {
throw new HttpException('http header must have a "demo-id" property', HttpStatus.BAD_REQUEST);
}
return await this.demoModel.findOne({ _id: demoId }).exec();
}
}
このGuardではヘッダーから取得したdemo-id
の値をkeyに、handleRequest
でDemo
というデータを取得してreturnしています。
Controllerではこのreturnした値を取得することができます。
4. 作成したGuardとデコレーターを使用したAPIを作成する
作成したGuardとデコレーターを使用し、Guardより取得した値を返却するだけのGET APIを作成します。
import { Controller, Get } from '@nestjs/common';
import { GuardResponse } from '../decorators';
import { DemoIdGuard } from '../guards';
import { Demo } from '../schemas/demo.schema';
@Controller('demo')
export class DemoController {
@UseGuards(DemoIdGuard)
@Get()
find(@GuardResponse() demo: Demo): Demo {
return demo;
}
}
まとめ
今回はGuardで取得した値をControllerで返却するだけのシンプルな実装であったため、GuardからControllerに値を渡すメリットは薄かったですが、Guardで複雑なバリデーションを実施し、その結果取得した値をControllerでも使いたい場合には、2度同じデータを取得する必要がなく、またGuardとContorollerとで役割を分割するNest.jsらしい実装も守ることができるので、とても見通しの良い実装ができると思います。
Author And Source
この問題について(Nest.jsでGuardからControllerへデータを渡す方法), 我々は、より多くの情報をここで見つけました https://qiita.com/biga816/items/bc0fb17725f9dc2b8525著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .