カテゴリー
アーカイブ

10.24
2025

FastAPI の認証機能を試してみる(その4 〜トークン無効化編〜)

  • LINE

※本記事は FastAPI の学習・理解を目的としたものであり、そのまま本番利用することを前提としていない点にご留意ください。

 

はじめに

前回までの記事 では、FastAPI の機能を活用して ID・パスワードでのログイン、JWT 形式でのアクセストークン発行、リフレッシュトークンを使ったアクセストークンのリフレッシュを実装してきました。

簡単に図でおさらいすると、下のようなフローになっています。

今回はログアウト処理を実装し、「FastAPI の認証機能を試してみる」のシリーズを締めたいと思います。

 

実装と動作確認

以下の手順でログアウトおよびトークン無効化処理を実装していきます。

1. リフレッシュトークン失効処理の追加
2. ログアウト用のエンドポイントの追加

ステップ1. リフレッシュトークン失効処理の追加

前回の実装では、ログイン時にリフレッシュトークンとユーザー ID の組を DB で保管し、トークンリフレッシュのリクエストがあった際に
・リフレッシュトークンのペイロードから取得したユーザー ID
・リフレッシュトークンに対応する、DB で保管されたユーザー ID
の二つを比較することで、そのユーザーが実際にログイン状態であるかを判定していました。

DB からリフレッシュトークンを破棄すればそのリフレッシュトークンは無効化されたものとして扱えるため、トークンを破棄する処理を追加します。

# database.py
def revoke_refresh_token(refresh_token: str) -> None:
    """リフレッシュトークンを失効させる"""
    if refresh_token in refresh_tokens_db:
        del refresh_tokens_db[refresh_token]
# auth.py
def revoke_tokens(refresh_token: str) -> None:
    """リフレッシュトークンを無効化する(ログアウト処理)"""
    revoke_refresh_token(refresh_token)

 

ステップ2. ログアウト用のエンドポイントの追加

ログアウト用のエンドポイントを追加し、その中でリフレッシュトークンを破棄する処理を呼び出します。

# routes/auth.py
from auth import (
    REFRESH_TOKEN_EXPIRE_SECONDS,
    create_new_access_token,
    create_tokens,
    revoke_tokens,
)

@router.post("/logout")
async def logout(
    response: Response,
    refresh_token: Annotated[str | None, Cookie()] = None,
) -> dict[str, str]:
    """ログアウトしてリフレッシュトークンを無効化する"""
    if refresh_token:
        # リフレッシュトークンを無効化
        try:
            revoke_tokens(refresh_token)
        except Exception:
            # トークンが既に無効でもエラーにしない
            pass

    # Cookieを削除(max_age=0で即座に削除)
    response.delete_cookie(key="refresh_token", samesite="lax")

    return {"message": "Successfully logged out"}

 

動作確認

実際に動作を見てみましょう。

# ログイン
$ curl -s -v -X POST "http://127.0.0.1:8000/auth/token" -H "Content-Type: application/x-www-form-urlencoded" -d "username=johndoe&password=secret"
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
> POST /auth/token HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 32
> 
* upload completely sent off: 32 bytes
< HTTP/1.1 200 OK
< date: Thu, 23 Oct 2025 08:14:42 GMT
< server: uvicorn
< content-length: 189
< content-type: application/json
< set-cookie: refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NjE4MTIwODN9.8-DW08yvbsSyZl7oTB-bQqKOHV3AFJVFSLqDeF8QiM4; HttpOnly; Max-Age=604800; Path=/; SameSite=lax
< 
* Connection #0 to host 127.0.0.1 left intact
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc2MTIwNzMxM30.OD8zN4_N2aEmjVPzSrr2Q8fWhA2xWOsboRcsSeLPTcs","token_type":"bearer"}

レスポンスヘッダーにリフレッシュトークンを確認できました。これを使ってアクセストークンのリフレッシュを試みます。

# トークンリフレッシュ
$ REFRESH_TOKEN="..."

$ curl -s -b "refresh_token=$REFRESH_TOKEN" -X POST "http://127.0.0.1:8000/auth/refresh"
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc2MTIwNzQ0MH0.JiXwWcfrvxiPmsmFk1cu70LO2Hm7lT8PP1j5o4Da_hs","token_type":"bearer"}

アクセストークンが再発行されていることを確認できました。では、ログアウトのリクエストを実行します。

# ログアウト
$ curl -s -b "refresh_token=$REFRESH_TOKEN" -X POST "http://127.0.0.1:8000/auth/logout"
{"message":"Successfully logged out"}

この状態で、再度アクセストークンのリフレッシュを試みます。

# トークンリフレッシュ
$ curl -s -b "refresh_token=$REFRESH_TOKEN" -X POST "http://127.0.0.1:8000/auth/refresh"
{"detail":"Invalid refresh token"}

期待通り、アクセストークンの再発行ができなくなっていることを確認できました!

 

補足

最後に発行したアクセストークンは有効期限が切れるまで使用できます。これは、現状の実装がアクセストークン単体で検証しているためです。アクセストークンの有効期間は基本的に短く設定するので、これが問題にならないケースもあるでしょう。厳密にアクセストークンも即時無効化したい場合は「ログアウト時のアクセストークンをブラックリストに登録し、無効なトークンとして扱う」という手法があります。

 

おわりに

ここまで 4 回に渡って FastAPI の認証機能に関する記事を書いてきました。実際に自分で実装してみることで、以前よりも認証のフローをイメージしやすくなったように思います。

一方、今回の実装で学べたのは認証・認可の全体で見るとごく一部だったなとも感じています。実装の中で OAuth2PasswordBearer というクラスを使っていましたが、OAuth2とは言いつつ実質はパスワード/トークンベース認証であり、「認可サーバー」「クライアント」「スコープ管理」など、本来の OAuth2 に関連する内容は扱えていません。

セキュリティ対策は日々ニュースになるほど重要なテーマであるため、今後も学び続けます。

 

 

"全員が技術者" ベイストリームは横浜発のPythonのプロフェッショナル集団です。
積極採用中!尖ったPythonエンジニアへの第一歩はこちらから