公式情報
- APIにbasic認証: https://fastapi.tiangolo.com/advanced/security/http-basic-auth/
- staticファイルにBasic認証: https://github.com/tiangolo/fastapi/issues/858
片方だけでいい場合は、公式の実装をそのまま利用すればよい。この記事では、両方にBasic認証をかけてみる。
API/static filesそれぞれにBasic認証を実装する
Basic認証用のモジュール
id/passは環境変数で指定する想定
basic_auth.py
import secrets
import os
from fastapi import HTTPException, status, Request, Depends
from fastapi.staticfiles import StaticFiles
from fastapi.security import HTTPBasic, HTTPBasicCredentials
security = HTTPBasic()
def auth_basic(credentials: HTTPBasicCredentials):
correct_username = secrets.compare_digest(
credentials.username, os.environ.get("BASIC_USER"))
correct_password = secrets.compare_digest(
credentials.password, os.environ.get("BASIC_PASSWORD"))
if not (correct_username and correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Basic"},
)
def verify_from_api(credentials: HTTPBasicCredentials = Depends(security)):
auth_basic(credentials)
class AuthStaticFiles(StaticFiles):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
async def __call__(self, scope, receive, send) -> None:
assert scope["type"] == "http"
request = Request(scope, receive)
credentials = await security(request)
auth_basic(credentials)
await super().__call__(scope, receive, send)
Basic認証用のモジュールを利用してコントローラを実装
from fastapi import FastAPI, HTTPException, status, UploadFile, File, Request, Depends
from basic_auth import verify_from_api, AuthStaticFiles
app = FastAPI()
app.mount("/static", AuthStaticFiles(directory="static"), name="static")
@app.get("/api/me")
def me(_ = Depends(verify_from_api)):
return {"message": "authorized"}
コードの解説
security = HTTPBasic()
- https://github.com/tiangolo/fastapi/blob/22528373bba6a654323de416ad5c867cbadb81bb/fastapi/security/http.py#L48
- Basic認証をするためのクラス
credentials = await security(request)
- https://github.com/tiangolo/fastapi/blob/22528373bba6a654323de416ad5c867cbadb81bb/fastapi/security/http.py#L61
- requestからauthorizationヘッダの情報を受け取る
- ここではバリデーションとbase64デコードのみ行い、実際の認証は行わない
- バリデーションNGは401レスポンスをraiseする。問題なければcredentials(デコードされたusername, passwordを保持するクラス)を返す
auth_basic(credentials)
- ここでユーザ名・パスワードの認証を行う
- 実体はこんな感じ
correct_username = secrets.compare_digest(credentials.username, os.environ.get("BASIC_USER"))
-
secrets.compare_digest
は、タイミング攻撃を防ぐような安全な文字列比較メソッド