⚠️
NestJSでpassportライブラリのAuthGuardを継承したGuardで、401エラーのデバッグをする方法
サンプルコード
JwtCognitoStrategy.ts
import {
Inject,
Injectable,
} from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Request } from 'express';
import { passportJwtSecret } from 'jwks-rsa';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AppConfigService } from '@server/config/config.service';
import { JwtProviderClaim } from './types';
@Injectable()
export class JwtCognitoStrategy extends PassportStrategy(
Strategy,
'jwtCognito',
) {
constructor(
@Inject('AppConfigService')
private configService: AppConfigService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
algorithms: ['RS256'],
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://cognito-idp.${configService.get(
'aws.region',
)}.amazonaws.com/${configService.get(
'cognito.userPoolId',
)}/.well-known/jwks.json`,
}),
passReqToCallback: true,
});
}
public async validate(
req: Request,
payload: JwtProviderClaim,
): Promise<string> {
req.body = { sub: payload.sub };
return payload.sub;
}
}
AuthGuard
import { ExecutionContext, Logger } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
export class JwtCognitoGuard extends AuthGuard('jwtCognito') {
constructor() {
super();
}
}
Auth.controller
// 省略
@UseGuards(JwtCognitoGuard)
@Post('/login')
async login(
@Body() requestBody: LoginWithUserAuthRequestBody,
): Promise<MyAuthenticationResponse> {
// 処理
}
ConfigServiceは何だって人はこちら
困ること
AuthGuardを使うと、Strategy内部のエラーが握りつぶされることがあります。エラーにUnauthorized
という情報しか返さなくて、どのようなエラーが発生したのかわからないのです。
@nestjs/passport
のauth.guard.js
の実装は以下の様になっており、errが存在するか、userがfalseになればエラーが投げられます。しかし、エラーの中には、infoにエラー内容が入っているものもあり、その際はエラー詳細がここで握りつぶされてしまいます。
@nestjs/passport auth.guard.js
handleRequest(err, user, info, context, status) {
if (err || !user) {
throw err || new common_1.UnauthorizedException();
}
return user;
}
実際に僕がハマったエラーは、JwtCognitoStrategy
で、環境変数を入れ忘れていたことで、jwksライブラリがそんなAPI存在しないよというものでした。
export class JwtCognitoStrategy extends PassportStrategy(
Strategy,
'jwtCognito',
) {
private logger = new Logger(JwtCognitoStrategy.name);
constructor(
@Inject('AppConfigService')
private configService: AppConfigService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
algorithms: ['RS256'],
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://cognito-idp.${configService.get(
'aws.region',
)}.amazonaws.com/${configService.get(
'cognito.userPoolId', // 👈ここ 環境変数入れ忘れてた
)}/.well-known/jwks.json`,
}),
passReqToCallback: true,
});
}
}
このときhandleRequest
には以下のような情報が渡されます。
handleRequest(null,false,{"message":"Bad Request","name":"JwksError"}, 省略)
第1引数がnullで、第2引数がfalseなので、これはエラーとして扱われます。しかし、エラーの内容が第3引数に入っています。
@nestjs/passport
のauth.guard.js
の実装は以下の様になっており、errが存在するか、userがfalseになればエラーが投げられます。しかし、エラーの中には、infoにエラー内容が入っているものもあり、その際はエラー詳細がここで握りつぶされてしまいます。
@nestjs/passport auth.guard.js
handleRequest(err, user, info, context, status) {
if (err || !user) {
throw err || new common_1.UnauthorizedException();
}
return user;
}
そこで、この時点でログをとっておくことで、エラー内容を拾うことができます。独自のAuthGuard
にhandleRequest
を定義して、そこでログを取ります。そのあと親のメソッドに渡してあげます。
AuthGuard
import { ExecutionContext, Logger } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
export class JwtCognitoGuard extends AuthGuard('jwtCognito') {
private logger = new Logger('JwtCognitoGuard');
constructor() {
super();
}
// 👇 追記
handleRequest(
...args: Parameters<
ReturnType<typeof AuthGuard>['prototype']['handleRequest']
>
) {
// エラーだけログに記録
// エラーとは、第1引数(err)があるか、第2引数(user)がfalseの場合
if (args[0] || !args[1]) {
this.logger.error(args);
}
return super.handleRequest(
args[0],
args[1],
args[2],
args[3] as any,
args[4],
);
}
}
こうすることで、無事に401エラーのデバッグができました!
Discussion