ども!今回はnest.jsでFirebaseクライアントから作成されたidTokenを検証するGuardを作成しました。トークン検証を実装することで、安全にユーザーを特定することができます。フロントからheaderに付与された情報を吸い出して、トークンが失効している場合はリソースへのアクセスをさせないようにしています。
ご挨拶
ども!爆速で検証内容をブログにしている龍ちゃんです。マルチでブログを執筆していると、ネタが交差してパンクしそうになりますね。忙しくなる前に過去の検証を急いで投稿しないといけないと焦って執筆しています。
さて!今回はバックエンドの話になります。過去記事で、フロントのみで構築していたアプリケーションをBFFを意識して分割していこうとしています。今回は実践編その2になります。
こちらでは、Firebaseのクライアントから送られてきたidTokenの検証をしていきたいと思います。クライアントでuidを取得することはできますが、そのまま運用するとなりすましのリスクなどもあるため、トークン検証を挟んでいきます。
今回記事でわかることは以下になります。
- nest.jsでFirebase SDKで発行したidTokenをFirebase admin SDKを使って検証する
事前準備
事前準備として、Firebase admin SDKを初期化する必要があります。基本的な部分は以下の記事で紹介しているので、まだの方は是非読んでみてください。
こちらの記事では、上記の記事のFirebase admin SDKの初期化が終了している前提で執筆しています。ご注意ください。
今回のディレクトリ構造は以下の想定で進めていきます。
.
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── common
│ └── guard
│ └── google-id-token-verify
│ ├── google-id-token-verify.guard.spec.ts
│ └── google-id-token-verify.guard.ts
├── config
│ ├── enviroments.module.ts
│ └── enviroments.service.ts
├── main.ts
└── types
└── todoType.ts
開発に必要なモジュールとしては、以下になります。
npm install @nestjs/config firebase-admin --save
それでは初めて行きます。
nest.jsでFirebase admin SDKを使ってidTokenを検証する
今回目指す構築の全体像について共有していきます。フロントエンドの実装としては、Firebase Autenticationで認証して、idToken
をaxiosのheader[”google-certification”]
として付与して送信してきます。nest.jsでは、headerを読み取りFirebase admin SDKを用いて検証します。
idToken検証用Guardの作成
公式リファレンスです。
common配下のguradにファイルを配置していきます。google-id-token-verify.guard.ts
を作成して以下の内容をコピーしてください。
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { EnvironmentsService } from 'src/config/enviroments.service';
import { getAuth } from 'firebase-admin/auth';
@Injectable()
export class GoogleIdTokenVerifyGuard implements CanActivate {
constructor(private readonly env: EnvironmentsService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const decodeToken = await getAuth(this.env.firebaseAppInstance).verifyIdToken(
request['headers']['google-certification'],
);
console.log(decodeToken);
if (!decodeToken) return false;
console.log(decodeToken.uid);
req.body.uid = decodeToken.uid;
return true;
}
}
内容としては、単純です。headerから情報を取得して、[verifyIdToken](<https://firebase.google.com/docs/auth/admin/verify-id-tokens?hl=ja#verify_id_tokens_using_the_firebase_admin_sdk>)
を用いてトークンの検証をしています。自力でもトークン検証を行うことはできますが、公式のライブラリがある場合は公式のライブラリを用いたほうが良いです。デコードの結果をbodyのuidとして挿入しています。こちらのコードでTokenからユーザーの特定を行うことができました。
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { GoogleIdTokenVerifyGuard } from './common/guard/google-id-token-verify/google-id-token-verify.guard';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@UseGuards(GoogleIdTokenVerifyGuard)
async getHello(): Promise<string> {
return this.appService.getHello();
}
@Post()
@UseGuards(GoogleIdTokenVerifyGuard)
postHello(@Body() request: { uid: string }): Promise<string> {
console.log(request.uid);
return this.appService.getHello();
}
}
なぜトークン検証が必要なのか?
なぜトークン検証が必要か?という話は一応まとめているのでこちらを参照してもらえればうれしいです。
もしトークン検証をしなかった世界線の話をしときます。認証を別のユーザーでして、userIDを別人の名前に変更した場合、ほかの人のリソースが覗けてしまう問題があります。
こちらを防止するために、リクエストを投げているユーザーをidTokenで確認する必要があります。
少し雑談なのですが、API的にHeaderのidTokenからユーザーIDを取得するべきなのでしょうか。今考えている内容では、リクエストボディにユーザーIDを入れようと考えています。有識者の方!教えてください。
nest.jsとAPI定義の型が異なるという点がネックになっています。
おわり
お疲れ様です。フロントとバックの検証を同時にしたので、脳が疲れています。デザインは実装していないのでおんなじ部分らへんを使っているはずなんですけどね。
今回の記事は、こちらの実装を前提に作成しています。フロントエンドにも興味がある方や前提条件を知りたい方はこちらにどうぞ!
では執筆も疲れたので寝ます!
Twitterのほうもよろしくお願いします。