created: 2021-05-04T13:06:35.000Z
NestJS の認証で、一部のルートだけ認証不要でアクセスさせたい
こちらの続編。
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();
}