以前に、NestJSのバージョン6くらいの時にできてたことが最新のバージョン(ver9系)だとできなくてかなりの時間を取られたので、解決方法を備忘録として残しておきます。
バージョン
$ nest --version
9.1.3
環境構築
プロジェクト作成
nest new jwt-test
cd jwt-test
JWT認証関連のパッケージインストール
リファレンスに従ってJWTに必要なパッケージをインストール
npm install --save @nestjs/passport passport
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt
※passport-localは今回使わないので、インストールしません
JwtStrategyの作成
まずはコンストラクタに何も指定しない状態で試します
src/auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'secret',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
JwtStrategyを使う
src/auth/auth.controller.ts
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';
@Controller({ path: 'auth' })
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
login(@Body() body: { username: string; password: string }) {
return this.authService.login(body);
}
/**
* 認証が必要な処理
* @returns
*/
@UseGuards(JwtAuthGuard)
@Get('test')
test() {
return '認証成功';
}
}
動作確認してみる
Talend API Testerで動確してみました
-
/auth/test
をトークンなしで呼び出すと401
-
/auth/login
でJWTを生成 -
/auth/test
にAuthorization: Bearer {JWT}
をヘッダに付与してもう一度呼び出します
本題:JwtStrategyで追加のチェックをしたい
JWTの認証が通った時に、ユーザーの持つ権限をDBから取得しようとしたんですが、ここでハマりました。。。
以前は以下のように直接インジェクション出来てたんですが、最新版ではできなくなっていました。
src/auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { AuthService } from 'src/auth/auth.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
// 直接インジェクション
constructor(private authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'secret',
});
}
async validate(payload: any) {
const userId = payload.sub;
// AuthServiceのメソッド呼び出し
const user = await authService.validate(userId);
return {
userId: user.id,
username: user.username,
authorities: user.authorities,
};
}
}
これでUnknown authentication strategy "jwt"
エラーとなりました。。。
解決法:moduleRefを使う
リファレンスにちっちゃく書いてありました
src/auth/auth.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
import { Request } from 'express';
import { AuthService } from './auth.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
// ModuleRefをインジェクション
constructor(private moduleRef: ModuleRef) {
super({
// こいつを指定すると、validateの第一引数にRequestが渡されるようになる
passReqToCallback: true,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'secret',
});
}
async validate(request: Request, payload: any) {
// サービスを取り出す(JwtStrategyでprovideしているサービスしか指定できない?)
const contextId = ContextIdFactory.getByRequest(request);
const authService = await this.moduleRef.resolve(AuthService, contextId);
const userId = payload.sub;
// UsersServiceのメソッド呼び出し
const user = await authService.validate(userId);
return {
userId: user.id,
username: user.username,
authorities: user.authorities,
};
}
}
説明を読む感じ、RequestScopeを使ってるとコンストラクタで直接インジェクションが出来ないってことですかね?
めちゃくちゃハマりましたが、無事に解決できてよかった。。。