🙂

ヘッダーの検証のためにNest.jsでInterceptorを実装する

2022/06/07に公開

やりたいこと

到達したリクエストのヘッダーを検証し、Invalidなら401を返し,ValidならAPIの呼び出しを許可する。

昔、AngularでもInterceptorを作ったのですが、それのNest.js版です。

https://qiita.com/SuyamaDaichi/items/03f88aaae592392fbb9a

環境

今回は、現時点で最新のNest.js 8系で行います。
説明用にできるだけシンプルに実装します。

基本的には公式ドキュメントに則ってます。

https://docs.nestjs.com/interceptors

手順

Nest CLIを使ってファイルを作成します

nest g in interceptor/request

コマンド一発でファイルと雛形を作ってくれるのはAngular/Nestの良いところの一つです。

https://docs.nestjs.com/cli/usages

それでできるInterceptorの雛形↓
ここにヘッダーを検証する処理を書いていきます。

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'

@Injectable()
export class RequestInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle()
  }
}

書いたらこんな感じ

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
  UnauthorizedException,
} from '@nestjs/common'
import { map, Observable } from 'rxjs'
import { checkToken } from 'src/util/validation'

@Injectable()
export class RequestInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest()
    const token = request.headers['authorization']
    // tokenを検証する処理
    const valid = checkToken(token)

    return next.handle().pipe(
      map((data) => {
        if (!valid) throw new UnauthorizedException()
        return data
      })
    )
  }
}

Interceptorの適用方法

3種類あります。

  • クラスレベル
  • メソッドレベル
  • グローバル

クラスレベル

コントローラー単位でInterceptorを適用します。

@UseInterceptors(new RequestInterceptor()) // Controllerにデコレーターとして呼び出す
@Controller('item')
export class ItemController {
  constructor(private itemService: ItemService) {}

  // 複数のアイテムを取得
  // 価格でのソートに対応
  @Get()
  async getItems(): Promise<item[]> {
    const items = await this.itemService.getItems()
    const response = items
    return response
  }

  // 単一のアイテムを取得
  @Get(':id')
  async getItem(@Param('id', new ParseIntPipe()) itemId: number): Promise<item> {
    const item = await this.itemService.getItem(itemId)
    return item
  }
}

メソッドレベル

メソッド単位でInterceptorを適用します。

@Controller('item')
export class ItemController {
  constructor(private itemService: ItemService) {}

  // 複数のアイテムを取得
  // 価格でのソートに対応
  @UseInterceptors(new RequestInterceptor()) // Methodにデコレーターとして呼び出す
  @Get()
  async getItems(): Promise<item[]> {
    const items = await this.itemService.getItems()
    const response = items
    return response
  }

  // 単一のアイテムを取得
  @Get(':id')
  async getItem(@Param('id', new ParseIntPipe()) itemId: number): Promise<item> {
    const item = await this.itemService.getItem(itemId)
    return item
  }
}

グローバルレベル

全てのAPIにInterceptorを適用します。

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalInterceptors(new RequestInterceptor()) // 追加
  await app.listen(3000)
}
bootstrap()

弾かれると、ちゃんと401を返してくれるようになります。
image.png

Discussion

ログインするとコメントできます