Contents
FastAPIでJWT認証を実装する前に準備すること
FastAPIアプリケーションにJWT認証を導入する際、事前準備の精度が開発のスムーズさに直接影響します。特に「python-joseライブラリの正しい使用方法」や「プロジェクト構成設計の明確化」といった点は、実装の根幹に関わる重要な要素です。以下では、JWT認証を導入する際の必須手順とベストプラクティスをステップバイステップで解説します。
必要なライブラリのインストール
FastAPIとJWT認証を実現するために、以下の4つのライブラリを使用することになります。これらが揃わないと、セキュリティや開発効率に支障が出ます。
| ライブラリ名 | 概要 |
|---|---|
| FastAPI | Webフレームワーク(主役) |
| python-jose | JWTの署名・検証処理を実装するライブラリ(pyJWTとの明確な区別が必要) |
| python-dotenv | .envファイルから環境変数を読み込むためのライブラリ |
| Uvicorn | ASGIサーバー |
プロジェクト初期化時の手順は以下の通りです:
- 仮想環境を作成し、アクティベートする
- 必要なパッケージをインストールする
|
1 2 3 4 5 6 7 |
# 仮想環境作成とアクティベート python -m venv venv source venv/bin/activate # Windowsの場合: venv\Scripts\activate # インストールコマンド(python-dotenvの導入を追加) pip install fastapi python-jose uvicorn python-dotenv |
注意:
python-joseはpyJWTと似た名前ですが、FastAPI公式ドキュメントでも推奨されているライブラリです。import文においてはfrom jose import jwtとして使用します。
プロジェクト構成の確認
プロジェクトフォルダ内に以下のファイル構造を整えることで、将来的な拡張性や保守性が向上します:
|
1 2 3 4 5 6 7 |
my_fastapi_app/ │ ├── main.py # FastAPIアプリケーションのエントリポイント ├── dependencies.py # 認証ミドルウェアや共通処理を定義 ├── models.py # Userモデルなどのデータ構造定義(追加推奨) └── .env # 環境変数を管理するファイル |
| ファイル名 | 内容 |
|---|---|
main.py |
FastAPIアプリのエントリポイント |
dependencies.py |
認証ミドルウェアや共通処理 |
.env |
シークレットキーなどの環境変数 |
トークン発行エンドポイントの実装手順
JWT認証では、ユーザーがログイン時にトークンを発行するエンドポイントが必要です。以下に、認証ロジックとJWTエンコード処理の具体例を示します。
認証ロジックの設計
トークン発行には以下の基本的なフローが必要です:
- ユーザーがIDとパスワードでリクエストを行う
- DBでユーザー情報を照合し、認証を実施
- 認証成功時にJWTトークンを生成して返す
注意:以下ではDBの接続は省略していますが、本番環境では必ずデータベースを使用してください。
JWTエンコード処理の実装例
以下にpython-joseを使用したJWTトークン発行コードを示します。Userモデルやoauth2_schemeの定義も含みます:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
from fastapi import Depends, FastAPI, HTTPException, status from pydantic import BaseModel from datetime import datetime, timedelta import jwt from typing import Optional # 環境変数読み込み from dotenv import load_dotenv import os load_dotenv() # 認証用のミドルウェア定義 from fastapi.security import OAuth2PasswordBearer oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") SECRET_KEY = os.getenv("JWT_SECRET_KEY") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 class TokenData(BaseModel): username: str | None = None class User(BaseModel): username: str password: str app = FastAPI() def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt @app.post("/token") def login(user: User): # 実際にはDBでユーザーを検証する if user.username == "test" and user.password == "password": access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"} raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username or password", headers={"WWW-Authenticate": "Bearer"}, ) |
JWT認証ミドルウェアの実装
トークン発行後、APIを保護するためには認証ミドルウェアが必須です。以下に、Depends機能を使用した実装とエラーハンドリングの例を示します。
トークン検証関数の作成
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from fastapi import Depends, HTTPException, status def get_current_user(token: str = Depends(oauth2_scheme)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception return User(username=username) except jwt.PyJWTError: raise credentials_exception |
保護されたルートへの適用例
認証ミドルウェアを依存性注入することで、以下のように保護できます:
|
1 2 3 4 |
@app.get("/users/me") def read_users_me(current_user: User = Depends(get_current_user)): return {"username": current_user.username} |
保護されたルートへのアクセス制御
認証ミドルウェアを適用した上で、セキュリティ対象のAPIにアクセス制限を設定します。以下に具体的な実装例とセキュリティベストプラクティスについて解説します。
セキュリティ対象APIの指定
保護されたルートはDependsで認証ミドルウェアを適用することで、アクセス制限が可能です:
|
1 2 3 4 |
@app.get("/protected") def protected_route(current_user: User = Depends(get_current_user)): return {"message": "This is a protected route", "username": current_user.username} |
認証失敗時のレスポンスカスタマイズ
以下のコードで、エラーメッセージを明確に表示できます:
|
1 2 3 4 5 6 |
raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="JWTの有効期限切れです", headers={"WWW-Authenticate": "Bearer"}, ) |
セキュリティベストプラクティス
JWT認証では、セキュリティリスクへの配慮が不可欠です。以下にSecretキー管理・トークン有効期限設定・非対称暗号の実装例を含む実践的なポイントを解説します。
Secretキーの管理方法
Secretキーは環境変数で管理し、コード内にハードコーディングしないことが必須です:
|
1 2 3 4 5 6 |
from dotenv import load_dotenv import os load_dotenv() SECRET_KEY = os.getenv("JWT_SECRET_KEY") |
.envファイルには以下のように記述します:
|
1 2 |
JWT_SECRET_KEY=your-very-secret-key-here |
セキュリティ対策として、
.gitignoreに.envを追加し、コード管理から除外してください。
トークン有効期限の設定
有効期限が長すぎる場合、リスクが高まります。以下のように短めに設定することが推奨されます:
|
1 2 |
ACCESS_TOKEN_EXPIRE_MINUTES = 15 # 短い期間でセキュリティを強化 |
非対称暗号方式(RS256)の導入例
非対称暗号は、公開鍵/秘密鍵を使用するため、セキュリティ性が高く推奨されます。以下に実装例を示します:
- 秘密鍵と公開鍵を作成(
openssl genrsa -out private_key.pem 2048) RS256を使用したトークンの生成・検証コード- 公開鍵で署名を検証、秘密鍵で署名を作成する方式
非対称暗号は運用がやや複雑になるため、初期実装時はHS256から始めて段階的に移行することをお勧めします。
HTTPS導入と通信のセキュリティ強化
JWTトークンは明文形式であるため、HTTPSでの通信が必須です。また、通信経路を暗号化することで、不正アクセスや盗聴リスクを大幅に軽減できます。
| セキュリティ対策 | 内容 |
|---|---|
| HTTPS導入 | 通信中のトークンの保護が可能になる |
| CORS設定 | ブラウザ側からの不正アクセスを防ぐ |
| XSS対策 | ユーザー入力データのエスケープ処理 |
以上の対策により、JWT認証の信頼性が向上します。