これまで他の言語で作っていたAPIを書き直しています。以前のAPIはFirebaseAuthorizationで認証していたので、FastAPIでもFirebaseAuthorizationでユーザー認証したい。さて、どうするか。
公式ライブラリでなんとかする
セキュリティ的にこれが無難だと思う。しかし、account_key.json
のような認証鍵をファイルとして渡す必要があり、Dockerコンテナ化する場合はボリュームのマウントがめんどくさくなってしまう。環境変数に全部乗せることができないだろうか?
サードパーティライブラリでなんとかする
実は、本当にFirebase Authorizationの認証だけがしたいならProjectIDさえあれば、署名が有効かどうかは 特定のエンドポイントにパラメータを添えてGETするだけで良い。(最悪IDトークン本体が1時間有効なJWTなので、本体をパースするだけでも確認できてしまうが、それが無効化されているかどうかを念の為Google鯖に確認しに行っているというだけ) それをすごくシンプルにできるようにしてるのがこのFastAPI-CloudAuth。本家firebase_adminと違い、深い依存関係が追加されることもないので、セキュア度が高くない用途であれば、このサードパーティでも良いと思う。今回作っているのは軽いゲームなので、今回はこっちを採用。
検証の実装例
非常にシンプルにできる仕様なので、実装も何もという感じだったが紹介。
from fastapi_cloudauth.firebase import FirebaseCurrentUser, FirebaseClaims
router = FastAPI()
get_current_user = FirebaseCurrentUser(project_id=os.environ["PROJECT_ID"])
@router.post(
"/users",
responses={
200: {"description": "OK"},
400: {"description": "Bad Request"},
401: {"description": "Unauthorized"},
409: {"description": "Conflict"},
},
tags=["users"],
summary="Add a user",
)
async def add_user(
auth: FirebaseClaims = Depends(get_current_user),
) -> None:
"""Add specified new user to server"""
# 認証できていればこの中にたどり着く
# できていなければ401が既に返されている
return auth.user_id # user_idが手に入ればあとは好きにどうぞ
テスト用のスタブ例
Headerに入ったBearer 以降の部分をそのままユーザーIDとして扱うことで、テスト時はトークン関係なしに直接試せるようにする。もっとやりようはあるが、愚直にやったらこうなった。あとはテストをしようとしたら、毎回認証がスキップされて本体だけのテストを行うことができる。
dependsHeader = Header(None)
async def get_current_user_stub(
authorization: Optional[str] = dependsHeader,
) -> FirebaseClaims:
if authorization is None:
raise HTTPException(status_code=401, detail="Not authenticated")
separated_authorization = authorization.split("Bearer ")
if len(separated_authorization) != 2:
raise HTTPException(status_code=401, detail="Not verified")
DUMMY_USER["user_id"] = separated_authorization[1]
return DUMMY_USER
get_current_user = FirebaseCurrentUser(project_id=os.environ["PROJECT_ID"])
from src.security_api import get_current_user, get_current_user_stub
@pytest.fixture
def app() -> FastAPI:
# ここにDependsの関数名をキーとして 別の関数を入れると オーバーライドができる
application.dependency_overrides[get_current_user] = get_current_user_stub
return application # type: ignore
やってみた感想
Firebase Authorizationは楽にできてすごく良い