概要
JWT(JSONWebToken)
を利用したpassport-jwt戦略
を実装した際の知見をまとめます
JWTとは、JSON形式で表現された認証情報などをURL文字列などとして安全に送受信できるよう、符号化やデジタル署名の仕組みを規定した標準規格。IETFによってRFC 7519として標準化されている。
JWT
の認証処理クラスを作成
JSONWebトークンでRESTfulエンドポイントを保護するためのpassport-jwt戦略を提供します
src/auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'
import { SCRIPT_ALERT_LIST } from 'src/utilities/masters.utility'
import { AuthService } from '../../../services/auth.service'
/**
* @description JWTの認証処理を行うクラス
*/
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
// 環境変数を読み込むために利用
private readonly configService: ConfigService,
// 認証ユーザーを返却する自作サービスクラス
private readonly authService: AuthService
) {
super({
// HTTPヘッダの Authorization bearerからトークンを読み込む
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
// トークンの有効期限を検証する
ignoreExpiration: false,
// 秘密鍵
secretOrKey: configService.get('auth.jwt_secret_key'),
})
}
/**
* JWTバリデーション
* @param req
* @param res
* @param next
* @returns
*/
async validate(req: Request) {
// APIに対してJWTトークンを含んだペイロード情報がリクエストされるので取得
const payload = req['payload']
const token = payload && payload.token ? payload.token : null
// 元の秘密鍵とリクエストされた秘密鍵が一致しない場合は認証を弾く
if (token && token === this.configService.get('auth.jwt_secret_key')) {
return await this.authService.findMe(payload.account_id)
} else {
throw new UnauthorizedException(SCRIPT_ALERT_LIST.AUTH_FAILURE)
}
}
}
JWT
を発行するサービスクラスを生成
発行したJWT
の中身を以下のサイトに貼り付けると内容を確認できます
src/services/auth.service.ts
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { InjectRepository } from '@nestjs/typeorm'
import * as bcrypt from 'bcrypt'
import * as jwt from 'jsonwebtoken'
import dayjs from 'src/commons/customs/dayjs'
import { Repository } from 'typeorm'
import {
AuthInterFace,
JwtPayload,
} from '../domain/interfaces/api-response/auth.interface'
@Injectable()
export class AuthService {
constructor() {
}
/**
* @description Jwtトークン発行
* @param {Account} account
* @param {string} path
* @return {*} {string}
* @memberof AuthService
*/
public createToken(account: AuthInterFace, path: string): string {
/**
* 有効期限を設定する
* string.endsWith でエンドポイントのURLにより期限を変更している
*/
const exp = path.endsWith(this.configService.get('auth.samplePath'))
? dayjs().add(1, 'year').toDate()
: dayjs().add(1, 'day').toDate()
// トークンに組み込むペイロード情報
const payload: JwtPayload = {
account_id: account.id,
login_id: account.login_id,
token: this.configService.get('auth.jwt_secret_key'),
}
// JWT トークンを発行します
return jwt.sign(
{ payload, exp: exp.valueOf() / 1000 },
this.configService.get('auth.jwt_secret_key')
)
}
}
ログイン時にJWT
をフロント側に返す
src/controllers/auth.controller.ts
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly terminalsService: TerminalService
) {}
/**
* @description ログイン-コンソール
* @param {LoginUserDto} loginUserDto
* @param {Request} req
* @return {*} {Promise<loginDataRO>}
* @memberof AuthController
*/
@Post('/login')
@UseGuards(AuthGuard('local'))
async login(
@Body() loginUserDto: LoginUserDto,
@Req() req: Request
): Promise<loginDataRO> {
const _account = await this.authService.findOne(loginUserDto)
return returnAccount(_account, req, this.authService)
}
/**
* @description ログインしたアカウントの情報を返却
* @param {Account} AuthInterFace
* @param {Request} req
* @param {AuthService} authService
* @return {*} {loginDataRO}
*/
const returnAccount = (
loginAccount: AuthInterFace,
req: Request,
authService: AuthService
): loginDataRO => {
if (!loginAccount) {
throw new UnauthorizedException()
}
const token = authService.createToken(loginAccount, req.url)
const { id, login_id, name } = loginAccount
const account = {
account_id: id,
name: name,
login_id: login_id,
token,
}
return { account }
}
ここまでで、APIエンドポイント
にログインリクエストを投げた後にJWT
を返却する処理が実装できたので、最後にJWT
の認証処理クラスをセットします
セットしたAPIエンドポイントにリクエストを投げる際にJWT
が無い or 期限が切れている 等があれば
リクエストを弾くことができるようになります
@UseGuards(AuthGuard('jwt'))
ContractorController.ts
import {
Controller,
UseGuards,
} from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { Request } from 'express'
@UseGuards(AuthGuard('jwt'))
@Controller('/contractors')
export class ContractorController {
constructor(private readonly contractorService: ContractorService) {}
}