Nest.jsでGuardからControllerへデータを渡す方法

Nest.jsでは[公式ドキュメント](https://docs.nestjs.com/guards)にも書いてあるとおり、Guardを作成する際には

1. `CanActivate`インターフェースを継承する
2. `canActivate`メソッド中でbooleanを返却する

という実装をすることで対象のリクエストの呼び出し可否をコントロールするというのが基本的な使い方です。
しかし、実務で色々なGuardを実装していると、Guardで取得したデータをControllerでも使いたくなるケースに時々出くわします。
GuardとControllerとで同じデータを取得する処理を重複して実装するのは無駄なので、今回はこのGuard内で取得したデータをControllerへと渡す実装をしてみようと思います。

# 1. Guardのベースとなる抽象化クラス「BaseGuard」を実装する
公式ドキュメントのPassportの実装を参考にします。
認証用モジュールである`@nestjs/passport`を利用する際には、Guard内の`handleRequest`メソッドの中で任意の検証をしつつ、ユーザー情報を返却するような実装をする必要があります。
例えば、公式ドキュメントにあるjwtの認証を行うGuardでは、下記の様な実装をしています。

```typescript:jwt-auth.guard.ts
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`メソッドで返却していたユーザーの情報になります。

```typescript
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

```typescript
    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に設定しているだけということです。
この実装を踏まえ、下記のようなベースとなる抽象化クラスを実装します。

```typescript
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']`からデータを取得するだけなので、下記の様な実装になります。

```typescript
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を作成します。

```typescript
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 this.demoModel.findOne({ _id: demoId }).exec();
  }
}
```

このGuardではヘッダーから取得した`demo-id`の値をkeyに、`handleRequest`で`Demo`というデータを取得してreturnしています。
Controllerではこのreturnした値を取得することができます。

# 4. 作成したGuardとデコレーターを使用したAPIを作成する
作成したGuardとデコレーターを使用し、Guardより取得した値を返却するだけのGET APIを作成します。

```typescript
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らしい実装も守ることができるので、とても見通しの良い実装ができると思います。