Firebase Auth custom token を使ってみる (Python)
BFF (backend for frontend) の認証に Firebase Authentication の custom token を使ってみる
前提
- BFF ユーザ管理は BFF サーバの認証とは別で自前で実装
- そのため自前でトークン発行とかしたい
- フロントエンド側の実装はここでは触れない
前準備
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)
トークンの払い出し
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
トークンの有効期限とかリフレッシュトークンとかクライアント側実装とか
触れないと言ったものの触りだけ。
ウェブ系ならこんな感じで受け取ったトークンをセットすれば良いらしい。
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 に貼ると検証可能。
トークンの検証
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 を使用
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
はトークンが取り消されているかどうかを確認する
都度確認すると実行速度にも影響あるかもなので、ユースケースやリスクによって判断する必要がありそう
番外編① Firebase コンソール
Firebase コンソールで signInWithCustomToken
でログインしたユーザが見れる。
プロバイダは未設定、ユーザー UID に発行時に渡した uid が入る模様。
番外編② Postman
Postman でテストする際にもあれこれしないと不便になってしまう。
調べてみるともう対応している方がいたので参考にさせてもらった。感謝🙏