Closed7

Firebase Auth custom token を使ってみる (Python)

YetAnother_ykYetAnother_yk

BFF (backend for frontend) の認証に Firebase Authentication の custom token を使ってみる

前提

  • BFF ユーザ管理は BFF サーバの認証とは別で自前で実装
    • そのため自前でトークン発行とかしたい
  • フロントエンド側の実装はここでは触れない
YetAnother_ykYetAnother_yk

前準備

Firebase コンソール からプロジェクトを作成する。
↑作成画面からすでにある Google Cloud プロジェクトに紐付けることもできる

Googleアナリティクスとの紐付けもできるみたいなので(今回のユースケースで意味あるかは不明)、必要であれば先にアナリティクスを用意しておくとスムーズ?

プロビジョニングが終わると「プロジェクトの概要」が表示される
Authentication を選択→始める

まずは Google Cloud とかと関係ない場所で BFF 自体は稼働しているので「サービス アカウント JSON ファイル」を使用して API を呼び出す
https://firebase.google.com/docs/auth/admin/create-custom-tokens?hl=ja#using_a_service_account_json_file

⚙️から「プロジェクトの設定」を開く→「サービスアカウント」タブ→「新しい秘密鍵を生成」で JSON ファイルを入手する

画面で提供されるサンプルコード
import firebase_admin
from firebase_admin import credentials

cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)
YetAnother_ykYetAnother_yk

トークンの払い出し

import firebase_admin
from firebase_admin import auth
from firebase_admin import credentials

FIREBASE_APP_NAME = "hoge"

def login(...):
    # 認証処理(省略)

    # ログインに問題なしならトークン発行処理に移る
    try:
        # すでに初期化されている場合は取得
        firebase_app = firebase_admin.get_app(FIREBASE_APP_NAME)
    except ValueError:
        # 初期化されていない場合は初期化
        cred = credentials.Certificate(
            "path/to/serviceAccountKey.json"
        )
        firebase_app = firebase_admin.initialize_app(
            credential=cred,
            name=FIREBASE_APP_NAME,
        )

    # firebase からカスタムトークンを取得
    additional_claims = {}  # JWT に入れ込みたい要素を格納する
    custom_token: bytes = auth.create_custom_token(
        str(user_id),  # 文字列でユーザを一意に特定できる要素を渡す
        additional_claims,
        app=firebase_app,
    )
    return custom_token.decode()  # bytes 型なので文字列にしてあげる

FIREBASE_APP_NAME は渡さないとデフォルト値 [DEFAULT] で初期化される
app=firebase_app も渡さない場合はデフォルトのアプリが参照される
初期化済みのものはプールされており、デフォルト含めてプール済み(未デリート)で同じ名前のものを初期化しようとするとエラーになる
もちろん、プールされていない名前のものにアクセスしようとしてもエラーになる
今回はアクセス待ち→あったら応答を繰り返すので初期化済みかもしれないし、まだかもしれないので取得しようとしてなかったら初期化とした

additional_claims は一部予約済みのキーがあるので注意 → https://firebase.google.com/docs/auth/admin/create-custom-tokens?hl=ja#reserved_custom_token_names

YetAnother_ykYetAnother_yk

トークンの有効期限とかリフレッシュトークンとかクライアント側実装とか

触れないと言ったものの触りだけ。
ウェブ系ならこんな感じで受け取ったトークンをセットすれば良いらしい。

firebase.auth().signInWithCustomToken(token)
  .then((userCredential) => {
    // Signed in
    var user = userCredential.user;
    // ...
  })
  .catch((error) => {
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

その他のクライアントのサンプルコード → https://firebase.google.com/docs/auth/admin/create-custom-tokens?hl=ja#sign_in_using_custom_tokens_on_clients

# ここからは chatGPT に聞いて裏取りしていない情報あり

トークンの有効期限は1時間
クライアント側のsdkで有効期限が切れる前に自動でリフレッシュトークンを使って新しいトークンを取得する
この辺りのロジックの実装が不要なので使いやすい

# ここまで

発行されるトークンは JWT なので文字列にしたものを jwt.io に貼ると検証可能。

YetAnother_ykYetAnother_yk

トークンの検証

ID トークンの取得

カスタムトークンを使って Firebase Authentication へのログインすると ID トークンが払い出されて、それを API のリクエストに使うイメージです
実用のコードは 一つ上のコメント を参照してください

ターミナルでの検証やテストコードは Firebase Auth REST API を使うと良さそう
Firebase Auth REST API: カスタム トークンを ID および更新トークンと交換

https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=[API_KEY]

API_KEY は Google Cloud コンソールから取得できる。
Firebase でここまでの作業を終えているとデフォルトで作成されているので、今回はそれを使った。
キーの制限は適宜行う必要がありそう

$ http POST 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=[API_KEY]' token='[custom_token]' returnSecureToken=true
HTTP/1.1 200 OK
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Encoding: gzip
Content-Type: application/json; charset=UTF-8
Date: 
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Server: ESF
Transfer-Encoding: chunked
Vary: Origin, X-Origin, Referer
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0

{
    "expiresIn": "3600",
    "idToken": "[idToken]",
    "isNewUser": false,
    "kind": "identitytoolkit#VerifyCustomTokenResponse",
    "refreshToken": "[refreshToken]"
}

※ HTTPie CLI を使用
https://github.com/httpie/cli

API リクエスト時にはこの取得した ID トークンも一緒に渡してあげる

ID トークンの検証

ID トークンのサーバサイド検証はドキュメントのままで動く
Firebase Authentication: ID トークンを検証する: Firebase Admin SDK を使用して ID トークンを確認する

def verify(...):
    try:
        # すでに初期化されている場合は取得
        firebase_app = firebase_admin.get_app(FIREBASE_APP_NAME)
    except ValueError:
        # 初期化されていない場合は初期化
        cred = credentials.Certificate(
            "path/to/serviceAccountKey.json"
        )
        firebase_app = firebase_admin.initialize_app(
            credential=cred,
            name=FIREBASE_APP_NAME,
        )

    # ID トークンを検証する
    try:
        # Verify the token
        decoded_token = auth.verify_id_token(key, app=firebase_app, check_revoked=True)
        print(decoded_token)  # 例えば uid -> 発行時に渡した uid
        return True
    except Exception as e:
        return False

ここで、key は受け取った ID トークン、firebase_app については前述通り、check_revoked=True はトークンが取り消されているかどうかを確認する
都度確認すると実行速度にも影響あるかもなので、ユースケースやリスクによって判断する必要がありそう

YetAnother_ykYetAnother_yk

番外編① Firebase コンソール

Firebase コンソールで signInWithCustomToken でログインしたユーザが見れる。
プロバイダは未設定、ユーザー UID に発行時に渡した uid が入る模様。

このスクラップは1ヶ月前にクローズされました