FastAPI + Firebase Auth調査
FastAPIとFirebase Authを使用して認証付きAPIサーバーを作ってみる。
開発環境
- Apple M2 16GB MacOS Ventura 13.2.1
- Python 3.9.6
使用するツール・ライブラリなど
- Firebase
- FastAPI v0.97.0
まずはAPIサーバーを構築する。
リクエストはPostmanを使って送信する。
まずは必要なライブラリをインストール。
fastapi==0.97.0
uvicorn==0.22.0
pydantic==1.10.9
python-dotenv==1.0.0
以下のコードを記述。
よく使うメソッドとしてgetとpostを適当な内容で定義する。
from fastapi import FastAPI
from pydantic import BaseModel
# サーバーの起動
app = FastAPI()
# リクエストボディの定義
class Message(BaseModel):
name: str
# getを定義
@app.get("/hello")
def read_root():
return {"message": "Hello, world!"}
# postを定義
@app.post("/hello")
def create_message(message: Message):
return {"message": f"Hello, {message.name}!"}
サーバーを起動してリクエストを送ってみる。
uvicorn main:app --reload
# getリクエスト結果
{
"message": "Hello, world!"
}
# postリクエスト結果
# pydanticで定義した型に従ってbodyにデータを含めておく
{
"message": "Hello, hada!"
}
確認できたのでAPIサーバーは一旦OK。
次にFirebase Authをセッティングする。
コンソールからポチポチして構築。
プロジェクトを作成する。
Authenticationに移動してログインプロバイダにメール/パスワードを設定しておく。
コンソールから直接ユーザーを追加する。既にメールアドレス確認済みのユーザーである前提で進める。
一旦ここまででOK。
次はPostmanから上記で作ったユーザー情報でログインしてIdTokenを取得してみる。
PostmanのPre-request Scriptを使用してIdTokenを取得して環境変数に設定するところまでやる。
getリクエスト(とりあえずスクリプト実行したいのでなんでもいい)にスクリプトを記述し、実行。
スクリプト内容 ※ 環境変数は事前に設定しておく
const postRequest = {
url: 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key='+ pm.environment.get('API_KEY'),
method: 'POST',
header: {
'Content-Type': 'application/json',
},
body: {
mode: 'raw',
raw: JSON.stringify({
email: pm.environment.get('AUTH_EMAIL'),
password: pm.environment.get('AUTH_PASSWORD'),
returnSecureToken: true
})
}
};
pm.sendRequest(postRequest, (error, response) => {
if (error) console.log(error);
const json = response.json();
pm.test('response json has idToken', () => {
pm.expect(json).to.have.own.property('idToken');
const idToken = json.idToken;
console.log('idToken', idToken)
pm.environment.set('AUTH_TOKEN', idToken);
});
});
事前設定が必要な環境変数
- API_KEY(firebaseのapiキー)
- AUTH_EMAIL(さっきの手順で登録したユーザーのemail)
- AUTH_PASSWORD(ユーザーのパスワード)
実行すると以下のように環境変数「AUTH_TOKEN」に取得できたIdTokenが入っている。
次はサーバー側にIdTokenを送信し、認証させるところまでやる。
スクリプトの参考コード(今回使用するにあたって少々書き換えた)
APIリファレンス
FastAPIで認証を行うのでFirebase Admin SDKを追加する。
pip install firebase-admin==6.1.0
requirements.txtにも追記しとく。
fastapi==0.97.0
uvicorn==0.22.0
pydantic==1.10.9
python-dotenv==1.0.0
+ firebase-admin==6.1.0
firebaseとの接続に秘密鍵も必要なのでコンソールから取得。
main.pyにSDK初期化コードを記述。
同時にgetとpost処理でdependsを使って認証をするようにする。
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
import firebase_admin
from firebase_admin import auth, credentials
cred = credentials.Certificate("./serviceAccountKey.json")
firebase_admin.initialize_app(cred)
# サーバーの起動
app = FastAPI()
# リクエストボディの定義
class Message(BaseModel):
name: str
# 認証関数の定義
def get_current_user(cred: HTTPAuthorizationCredentials = Depends(HTTPBearer())):
if not cred:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"}
)
try:
cred = auth.verify_id_token(cred.credentials)
except:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"}
)
return cred
# getを定義
@app.get("/hello")
def read_root(cred = Depends(get_current_user)):
uid = cred.get("uid")
return {"message": f"Hello, {uid}!"}
# postを定義
@app.post("/hello")
def create_message(message: Message, cred = Depends(get_current_user)):
uid = cred.get("uid")
return {"message": f"Hello, {message.name}! Your uid is [{uid}]"}
ここまでできたら認証処理の作成が完了。
postmanで試してみる。
# getリクエスト結果
{
"message": "Hello, YBb3j5o2VKZqe2zrMJCq5gx6Qap2!"
}
# postリクエスト結果
# pydanticで定義した型に従ってbodyにデータを含めておく
{
"message": "Hello, hada! Your uid is [YBb3j5o2VKZqe2zrMJCq5gx6Qap2]"
}
# 認証失敗時
{
"detail": "Not authenticated"
}
認証成功でユーザーのuidが表示され、認証に失敗するとエラーメッセージが返ってくる。
あとはクッキーにセッショントークン詰めて返すとかしてセッション管理できればなお良しだけど、目的は果たせたのでこれにて終了。
追記
参考記事