L14 ch02 — 認証 (JWT)
テクノロジ系 / L14 middleware — ミドルウェア
L14 ch02 — 認証 (JWT)
「誰がアクセスしているか」を毎リクエスト判定する最小例
ゴール
- JWT (JSON Web Token) の役割を理解
- ログインで token を発行できる
- 保護されたエンドポイントを
Authorizationヘッダで認証できる
なぜ認証が必要か
HTTP は ステートレス = 毎リクエスト「誰か」が分からない
GET /tasks ← これ、誰のタスク?
→ クライアントが毎回「私は X です」と証明する必要がある
→ 証明書代わりに token を渡す
JWT (JSON Web Token) とは
文字列 1 本で「ユーザー情報 + 改ざん防止署名」を表現する規格
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0YXJvIn0.signatureXYZ └── header ──┘ └── payload (誰か) ─┘ └── 署名 ─┘
- payload に
user_idなどを入れる - 秘密鍵で署名 → 改ざんされたら検証で弾ける
- Cookie / セッション DB が不要 (= ステートレス API 向き)
認証の流れ
[1] POST /login {user, password}
↓
サーバー: 認証成功 → JWT を発行して返す
[2] GET /me Authorization: Bearer <JWT>
↓
サーバー: JWT を検証 → ユーザー特定 → レスポンス最小例: token 発行
from datetime import datetime, timedelta, timezone
import jwt # PyJWT
SECRET = "demo-secret-key" # 本番では環境変数
ALGO = "HS256"
def create_token(username: str) -> str:
payload = {
"sub": username,
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
}
return jwt.encode(payload, SECRET, algorithm=ALGO)最小例: token 検証
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
oauth2 = OAuth2PasswordBearer(tokenUrl="login")
def current_user(token: str = Depends(oauth2)) -> str:
try:
payload = jwt.decode(token, SECRET, algorithms=[ALGO])
return payload["sub"]
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Invalid token")エンドポイントに繋ぐ
@app.post("/login")
def login(username: str, password: str):
# ※デモ: パスワード固定。本番は DB + ハッシュ照合
if password != "secret":
raise HTTPException(status_code=401, detail="Bad credentials")
return {"access_token": create_token(username), "token_type": "bearer"}
@app.get("/me")
def me(user: str = Depends(current_user)):
return {"user": user}リクエスト例
# 1. ログインして token を取得
curl -X POST "http://localhost:8000/login?username=taro&password=secret"
# → {"access_token":"eyJhbGc...","token_type":"bearer"}
# 2. token を付けて保護エンドポイントを叩く
curl http://localhost:8000/me \
-H "Authorization: Bearer eyJhbGc..."
# → {"user":"taro"}やってはいけないこと
| NG | 理由 |
|---|---|
| SECRET をコードに直書き (本番) | git に漏れる → 全 token が偽造可能 |
exp (有効期限) を入れない | 一度漏れた token が永久に使える |
| パスワードを平文で DB 保存 | 漏洩時に致命的 (bcrypt 等でハッシュ化) |
| token を URL のクエリに付ける | ログに残る → 漏洩リスク (ヘッダで送る) |
drill
drill/ — fill-jwt
create_token / current_user の ___ を埋めて JWT 認証を完成させる
よくある詰まり
| 症状 | 原因 | 対処 |
|---|---|---|
| 401 Invalid token | SECRET / アルゴリズムが不一致 | encode と decode で揃える |
ExpiredSignatureError | exp を過ぎた | 再ログインで再発行 |
KeyError: 'sub' | payload に sub を入れていない | encode 側で "sub": username |
次に学ぶ領域
| 領域 | 続けて読む |
|---|---|
| パスワードのハッシュ化 | passlib[bcrypt] (= security/ch01) |
| Refresh token / token 失効 | 上位章 (本教材スコープ外) |
| OAuth2 連携 (Google など) | fastapi.security の他のクラス |
力試ししたい方は クイズ形式の演習(任意) もあります。読むだけでも十分です。