created: 2021-05-04T13:06:35.000Z

NestJS の認証で、一部のルートだけ認証不要でアクセスさせたい

こちらの続編。

NestJS の App#useGlobalGuards を使うと、すべてのルート(エンドポイント)に対して認証をかけることができる。ルートを実装するごとにいちいち依存を定義してデコレータでGuardをつけるのは面倒なのでこの機能はとても便利である。 しかし、一部のルートだけ認証せずにアクセスさせたい場合がある。たとえば、Prometheus からアクセスされるスコアボードなどは認証をかけたくない。

そういうときに一部のルートだけ明示的にPublicにするための方法が GitHub issue で案内されていた。

方法

  • @nestjs/commonSetMetadata を使い、ルートがパブリックであることを明示できるデコレータを実装する
  • 認証を行うGuardの中でルートのコンテキストを参照し、もしルートがパブリックなら認証処理をせずにコントローラに処理を受け渡す実装をする
  • 認証を不要にしたいルートに件のデコレータを設定する

認証をしないためのデコレータ

SetMetadata はルートにかけるデコレータをつくるためのヘルパーである。ここで設定されたメタデータは Reflector を通してGuardの中で参照される。

import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

認証処理でルートのメタデータを参照する

Guard の canActivate を override して、Reflector からルートに設定されたコンテキストを参照する。

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  // override
  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    // ルートの `IS_PUBLIC_KEY` フラグがたっていたらtrueを返してしまう
    if (isPublic) {
      return true;
    }
    return super.canActivate(context);
  }
}

Reflector を Guard に渡す

JwtAuthGuard のコンストラクタで Reflector が必要になったので引数で渡すようにする。

  const reflector = app.get(Reflector);
  app.useGlobalGuards(new JwtAuthGuard(reflector));

パブリックにしたいルートにデコレータを設定する

import { Public } from './auth/public';

...

  @Public()
  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

参考

失敗から学ぶユーザインタフェース 世界はBADUI(バッド・ユーアイ)であふれている
[ad] 失敗から学ぶユーザインタフェース 世界はBADUI(バッド・ユーアイ)であふれている
中村 聡史 (大型本)