NestJS の認証で、一部のルートだけ認証不要でアクセスさせたい
May 4, 2021 22:06 · 697 words · 2 minute read
こちらの続編。
NestJS の App#useGlobalGuards を使うと、すべてのルート(エンドポイント)に対して認証をかけることができる。ルートを実装するごとにいちいち依存を定義してデコレータでGuardをつけるのは面倒なのでこの機能はとても便利である。
しかし、一部のルートだけ認証せずにアクセスさせたい場合がある。たとえば、Prometheus からアクセスされるスコアボードなどは認証をかけたくない。
そういうときに一部のルートだけ明示的にPublicにするための方法が GitHub issue で案内されていた。
方法
- @nestjs/commonの- SetMetadataを使い、ルートがパブリックであることを明示できるデコレータを実装する
- 認証を行う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();
  }