Closed7

FastAPI + Firebase Auth調査

Ryotaro HadaRyotaro Hada

FastAPIとFirebase Authを使用して認証付きAPIサーバーを作ってみる。

開発環境

  • Apple M2 16GB MacOS Ventura 13.2.1
  • Python 3.9.6

使用するツール・ライブラリなど

  • Firebase
  • FastAPI v0.97.0
Ryotaro HadaRyotaro Hada

まずはAPIサーバーを構築する。
リクエストはPostmanを使って送信する。

まずは必要なライブラリをインストール。

requirements.txt
fastapi==0.97.0
uvicorn==0.22.0
pydantic==1.10.9
python-dotenv==1.0.0

以下のコードを記述。
よく使うメソッドとしてgetとpostを適当な内容で定義する。

main.py
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。

Ryotaro HadaRyotaro Hada

次にFirebase Authをセッティングする。
コンソールからポチポチして構築。

https://firebase.google.com/

プロジェクトを作成する。

Authenticationに移動してログインプロバイダにメール/パスワードを設定しておく。

コンソールから直接ユーザーを追加する。既にメールアドレス確認済みのユーザーである前提で進める。

一旦ここまででOK。
次はPostmanから上記で作ったユーザー情報でログインしてIdTokenを取得してみる。

Ryotaro HadaRyotaro Hada

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を送信し、認証させるところまでやる。

Ryotaro HadaRyotaro Hada

FastAPIで認証を行うのでFirebase Admin SDKを追加する。

pip install firebase-admin==6.1.0

requirements.txtにも追記しとく。

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を使って認証をするようにする。

main.py
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が表示され、認証に失敗するとエラーメッセージが返ってくる。
あとはクッキーにセッショントークン詰めて返すとかしてセッション管理できればなお良しだけど、目的は果たせたのでこれにて終了。

このスクラップは2023/06/14にクローズされました