LoginSignup
7
2

More than 1 year has passed since last update.

API GatewayのJWT検証にFirebase Authenticationを使う

Last updated at Posted at 2022-05-27

こんにちは。

今、こんな感じのアーキテクチャでサービスを作ろうとしています(簡略化してます)

image.png

IDaaSとして過去の資産が使えるFirebase Authenticationを利用し、認証済みのユーザーのみAPI Gatewayから先のAPIにアクセスできるというアーキテクチャです。

API GatewayのJWT AuthorizerをGoとかPythonで作ってる記事はありましたが、Node.js & Typescriptでの日本語記事が無かったので、書いておきます。

Goの実装例

Javascriptの実装例

環境

全部Typescriptでやります。
Lambdaの実行環境は、Node.js16.x

処理の流れ

  1. クライアント(今回はExpoですがWebでもOK)でFirebase Authenticationでログインする
  2. auth.currentUser.getIdToken()でJWTをクライアント側で保持しておく
  3. クライアントからなんらかのAPIにリクエストするときに、2で保持しているJWTをヘッダーに付与してリクエストする
  4. API GatewayはAuthorizer(Lambda)を通して、受け取ったJWTが有効なものかLambdaを呼び出して検証する
  5. 有効なJWTであれば、先のAWSサービス(今回はApp Runnerを使いますが割愛します)をCallする。そうでなければ403を返す(本来は401を返すべきですが、API Gatewayのデフォが403?)

説明すること、しないこと

この部分を説明します。
image.png

API Gatewayから先は今回関係ないので割愛し、Mockを設定してます。
image.png

また、今回Typescriptで実装するにあたり、AWS SAMを使用しています。つい最近TSがベータ版ながらサポートされたとのことでありがたく使わせていただいてます。

また、生のLambda(?)でnpm packageを使うためにはLayerという機能を使わないといけないのですが、SAMであればよしなにやってくれるので、下記のような操作が不要になりDXが向上するのでおすすめです。

今回はSAMの使い方は説明しません、とにかく firebase-adminが使えるLambda関数をデプロイできればServelessFrameworkでも、生でもOKです。

SAMについてはこちらが参考になりました。

JWT Authorizer コード例

import { APIGatewayAuthorizerResult, APIGatewayAuthorizerEvent } from 'aws-lambda'
import { auth } from 'firebase-admin'
import { cert, initializeApp } from 'firebase-admin/app'

// Lambdaの環境変数(FIREBASE_DATABASE_ENDPOINT)にFirebaseProjectのdatabaseurlを登録しておく。
const firebaseDatabaseEndpoint = process.env.FIREBASE_DATABASE_ENDPOINT

// FirebaseAdminSDKを使うための秘密鍵ファイルを指定する
// eslint-disable-next-line @typescript-eslint/no-var-requires
const serviceAccount = require('./serviceAccount')

initializeApp({
  credential: cert(serviceAccount),
  databaseURL: firebaseDatabaseEndpoint
})

/** ポリシーを生成 */
const generatePolicy = (
  principal: string,
  effect: 'Allow' | 'Deny',
  resource: string
): APIGatewayAuthorizerResult => {
  return {
    principalId: principal,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [
        {
          Action: 'execute-api:Invoke',
          Effect: effect,
          Resource: resource,
        },
      ],
    },
  }
}

export const lambdaHandler = async (event: APIGatewayAuthorizerEvent): Promise<APIGatewayAuthorizerResult> => {
  if (event.type !== 'TOKEN') {
    console.log(`expected authorization type is TOKEN, got ${event.type}`)
    return generatePolicy(null, 'Deny', event.methodArn)
  }

  // JWTを取得
  const token = event.authorizationToken

  if (!token) {
    console.log('authorization token must not bet null')
    return generatePolicy(null, 'Deny', event.methodArn)
  }

  // JWTを検証
  try {
    const result = await auth().verifyIdToken(token)
    return result.uid
      ? generatePolicy(result.uid, 'Allow', event.methodArn)
      : generatePolicy(null, 'Deny', event.methodArn)
  } catch (error) {
    console.log('authorization token is invalid')
    return generatePolicy(null, 'Deny', event.methodArn)
  }

}

作成したJWT Authorizerをアタッチする

SAMでできるっぽいですが、まだ勉強不足のためGUIでアタッチしました。

オーソライザーを作成

API Gatewayで、オーソライザーを作成します。
先ほど作った関数を指定します。トークンのソースはスタンダードにAuthorizationとしました。クライアントからのリクエストはここで指定したヘッダーに付与します。
image.png

対象のエンドポイントに先ほど作成したオーソライザーを指定します。
image.png

デプロイするのを忘れずに!

デプロイしたAPIにリクエストして確認。

正しいJWT
image.png
誤ったJWT
image.png

ヨシ!

あとは

AWS Serviceに繋ぐ前にLambdaの関数を挟んでいるので、JWTの検証だけでなく、ユーザーごとのリクエストのログを取ったりできそうですね。

参考

https://dev.classmethod.jp/articles/lambda-authorizer-verify-token-from-auth0/
https://blog.devgenius.io/secure-your-apis-with-firebase-aws-api-gateway-199b1eda1da3
https://blog.ymcloud.jp/entry/firebase-auth-aws-integration/

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2