NestJSでPassportを利用し、下記の最低限なJWT認証機能を実装してみる。
エンドポイント | 機能 |
---|
/auth/signup | メールアドレス・パスワードで新規登録 |
/auth/login | メールアドレス・パスワードでログイン(アクセストークンを返す) |
/users/profile | 自身の登録情報を参照する(アクセストークンが有効な場合のみ) |
前提
NestJS(Fastify) × TypeORM × PostgreSQL × Dockerで環境構築
NestJS × TypeORM 0.3 でCLIからmigrationする
上記2記事に沿って環境構築が完了し、DBにusers
テーブルを作成できている前提で進める。
1. 各ライブラリのインストール
2. 新規登録機能
2-1. Authリソースの作成
2-2. DTOの作成
新規登録とログインの両方で利用するDTOを作成する。
2-3. Pipeの有効化
class-validator
のバリデーションをグローバルで有効化しておく。
User
の保存と取得用のメソッドを追加する。
2-5. UsersModuleの修正
UsersService
でUsersRepository
が利用できるようにimportし、後にAuth
モジュールでもDIするのでexportする。
2-6. 型定義
2-7. AuthModuleの修正
AuthService
でUsersService
をDIするためにUsersModule
をimportする。
2-8. AuthServiceの実装
- DTOで受け取った
email
とpassword
からユーザーを作成
- もし同一の
email
がすでに存在する場合はConflictException
を投げる
2-9. AuthControllerの実装
POSTのsignup
メソッドを作成し、先ほど作成したAuthService.login()
にDTOを渡す。
2-10. 動作確認
適当なemail
とpassword
をbodyに含めてPOSTしてみると、上記レスポンスが返りusers
テーブルにもレコードが保存されていた。
DTOで設定したバリデーションにわざと失敗するようにPOSTしてみても、無事Bad Requestが返ってきた。
3. ログイン(認証)機能
3-1. 型定義
3-2. AuthServiceの修正
JwtService
をDIする
- 正常な
email
とpassword
かを検証するvalidateUser`メソッドの実装
bcrypt.compare()
でハッシュ化前と後のパスワードを比較し検証
- レスポンスにパスワードを含めたくないので
User
オブジェクトから`hashedPasswordを除外して返す
- アクセストークンを生成する
login
メソッドの実装
userId
とemail
をペイロードに指定
- 今回は
User
エンティティに最低限のカラムしか追加してないのでこのようにしているが、本来はメールアドレスのような情報はペイロードに含めるべきでないので注意
3-3. LocalGuardの実装
/auth/login
にアクセスした際にまず有効なemail
・password
かどうかを検証する必要がある。
そのために今回はLocalStrategy
をクラス化したLocalGuard
を実装する。
- passport-localはデフォルトで
username
とpassword
を受け取るようになっているが、今回はemail
とpassword
の組み合わせにしたいのでusernameField
を指定
validate
メソッド内では、先ほど実装したauthService.validateUser()
を呼び出しUserをそのまま返す
LocalStrategy
をGuardクラス化する。
3-4. AuthModuleの修正
トークンの生成に使用する秘密鍵を環境変数として登録する。
JwtModule
をimportする
useFactory
でConfigService
をDIし、秘密鍵を参照
providers
にLocalStrategy
を指定
3-5. AuthControllerの修正
ログイン用のエンドポイントを作成する。
@UseGuards(LocalGuard)
とすることでlogin()
が実行される前にemail
・password
が検証され、無事成功すればLocalStrategy.validate()
から返したUser
がreq.user
が取得できる。
3-6. 動作確認
先程登録したユーザーでPOSTしてみると、無事アクセストークンが返ってきた。
適当なemail
・password
を指定すると、無事LocalStrategy
での検証に失敗している。
4. 認可機能
4-1. JwtGuardの実装
有効なアクセストークンを保持しているかどうかを検証するGuardを実装する。
passport-jwt
のStrategyを継承し、super()
を呼び出す際にオプションを指定(参考)
- トークンを検証する必要はないので、
validate()
ではペイロードのemail
からUser
を取得しhashedPassword
を除外して返すだけ
4-2. AuthModuleの修正
4-3. UsersControllerの実装
JwtGuard
で返されたUser
の情報をそのまま返すだけのprofile()
を実装する。
4-4. 動作確認
/auth/login
を叩いて取得したアクセストークンをAuthorizationヘッダ内に含め/users/profile
にGETすると、無事ログインしたユーザーの情報が返ってきた。
不正なアクセストークンを指定してPOSTすると、無事認可に失敗した。
1234