FastAPI Users
認証ライブラリ
FastAPI Users
概要
FastAPI Usersは、FastAPI専用の包括的な認証・ユーザー管理ライブラリです。2025年のFastAPI急速普及に伴い、非同期アプリケーション開発のスタンダードとして大幅に成長しています。JWT、Cookie、OAuth認証を統合サポートし、完全非同期対応による高性能とプラガブルな設計により、モダンAPI開発における認証標準として確立されています。SQLAlchemy、Beanieなどの主要ORMとの統合により、エンタープライズレベルのユーザー管理システムを迅速に構築できます。
詳細
FastAPI Usersは、FastAPIアプリケーション向けの完全なユーザー管理エコシステムです。完全非同期対応により高性能を実現し、プラガブルなアーキテクチャによって柔軟なカスタマイズが可能です。
技術的特徴
- 完全非同期対応: FastAPIのAsync/Await機構を最大活用した高性能処理
- 複数認証バックエンド: JWT、Cookie、Database、Redis戦略の組み合わせ対応
- プラガブル設計: Transport(トークン伝送方法)とStrategy(トークン生成方式)の分離
- OAuth2統合: Google、GitHub等の主要プロバイダーとの簡単連携
- データベース非依存: SQLAlchemy、Beanie(MongoDB)等複数ORM対応
- OpenAPIスキーマ: 自動ドキュメント生成とSwagger UI統合
2025年における位置づけ
FastAPIの普及加速により、FastAPI Usersは非同期Web開発のデファクトスタンダードとなっています。現代的なマイクロサービスアーキテクチャや、リアルタイム通信を要求するアプリケーションに最適化された設計です。
メリット・デメリット
メリット
- 開発効率: 即使用可能なルートとコンポーネントで開発加速
- 高性能: 完全非同期処理による優秀なスループット
- 柔軟性: モジュラー設計による高いカスタマイズ性
- セキュリティ: 業界標準のセキュリティプラクティス内蔵
- スケーラビリティ: 複数認証戦略による拡張性
- TypeScript連携: FastAPIの型ヒント活用によるタイプセーフティ
- エコシステム: 豊富なサンプルコードと活発なコミュニティ
デメリット
- 学習コスト: FastAPI固有の設計パターン習得が必要
- 依存関係: FastAPIエコシステムへの強い依存
- 設定複雑度: 高度な機能活用時の設定複雑性
- JWT制限: JWTトークンのサーバーサイド無効化不可
- 機能過多: 小規模プロジェクトには複雑すぎる場合がある
参考ページ
書き方の例
基本的なインストールとセットアップ
# 基本インストール(SQLAlchemy バックエンド付き)
pip install fastapi-users[sqlalchemy]
# OAuth機能付きでインストール
pip install fastapi-users[sqlalchemy,oauth]
# Beanie(MongoDB)バックエンド付きでインストール
pip install fastapi-users[beanie]
# その他の依存関係
pip install uvicorn[standard] aiosqlite
基本的なユーザーモデルとデータベース設定
# db.py - SQLAlchemyによるユーザーモデル定義
import uuid
from typing import AsyncGenerator
from fastapi import Depends
from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
from sqlalchemy import String
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(SQLAlchemyBaseUserTableUUID, Base):
"""ユーザーテーブルの定義"""
# 基本フィールドは SQLAlchemyBaseUserTableUUID から継承
# id: UUID (Primary Key)
# email: str (Unique)
# hashed_password: str
# is_active: bool
# is_superuser: bool
# is_verified: bool
# カスタムフィールドの追加
first_name: Mapped[str] = mapped_column(String(50), nullable=True)
last_name: Mapped[str] = mapped_column(String(50), nullable=True)
phone_number: Mapped[str] = mapped_column(String(20), nullable=True)
# データベース設定
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
engine = create_async_engine(DATABASE_URL)
async_session_maker = async_sessionmaker(engine, expire_on_commit=False)
async def create_db_and_tables():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
yield session
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(session, User)
Pydanticスキーマの定義
# schemas.py - リクエスト/レスポンススキーマ定義
import uuid
from typing import Optional
from fastapi_users import schemas
class UserRead(schemas.BaseUser[uuid.UUID]):
"""ユーザー情報読み取り用スキーマ"""
first_name: Optional[str] = None
last_name: Optional[str] = None
phone_number: Optional[str] = None
class UserCreate(schemas.BaseUserCreate):
"""ユーザー作成用スキーマ"""
first_name: Optional[str] = None
last_name: Optional[str] = None
phone_number: Optional[str] = None
class UserUpdate(schemas.BaseUserUpdate):
"""ユーザー更新用スキーマ"""
first_name: Optional[str] = None
last_name: Optional[str] = None
phone_number: Optional[str] = None
ユーザーマネージャーと認証バックエンドの設定
# users.py - ユーザーマネージャーと認証設定
import uuid
from typing import Optional
from fastapi import Depends, Request
from fastapi_users import BaseUserManager, UUIDIDMixin
from fastapi_users.authentication import (
AuthenticationBackend,
BearerTransport,
CookieTransport,
JWTStrategy,
)
from .db import User, get_user_db
SECRET = "your-secret-key-here" # 本番では環境変数を使用
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
reset_password_token_secret = SECRET
verification_token_secret = SECRET
async def on_after_register(self, user: User, request: Optional[Request] = None):
print(f"ユーザー {user.id} が登録されました。")
async def on_after_forgot_password(
self, user: User, token: str, request: Optional[Request] = None
):
print(f"ユーザー {user.id} がパスワードリセットを要求しました。トークン: {token}")
async def on_after_request_verify(
self, user: User, token: str, request: Optional[Request] = None
):
print(f"ユーザー {user.id} の認証トークン: {token}")
async def validate_password(
self,
password: str,
user: User | UserCreate,
) -> None:
if len(password) < 8:
raise ValueError("パスワードは8文字以上である必要があります")
if not any(char.isdigit() for char in password):
raise ValueError("パスワードには少なくとも1つの数字を含める必要があります")
async def get_user_manager(user_db=Depends(get_user_db)):
yield UserManager(user_db)
# 認証バックエンド設定
# JWT Bearer認証
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
def get_jwt_strategy() -> JWTStrategy:
return JWTStrategy(secret=SECRET, lifetime_seconds=3600)
jwt_backend = AuthenticationBackend(
name="jwt",
transport=bearer_transport,
get_strategy=get_jwt_strategy,
)
# Cookie認証
cookie_transport = CookieTransport(cookie_max_age=3600)
cookie_backend = AuthenticationBackend(
name="cookie",
transport=cookie_transport,
get_strategy=get_jwt_strategy,
)
# 複数バックエンドを使用
auth_backends = [jwt_backend, cookie_backend]
FastAPIアプリケーションのセットアップ
# app.py - メインアプリケーション
import uuid
from fastapi import FastAPI, Depends
from fastapi_users import FastAPIUsers
from .db import User, create_db_and_tables
from .schemas import UserCreate, UserRead, UserUpdate
from .users import auth_backends, get_user_manager
# FastAPIUsers インスタンス
fastapi_users = FastAPIUsers[User, uuid.UUID](
get_user_manager,
auth_backends,
)
# 現在のユーザー取得関数
current_active_user = fastapi_users.current_user(active=True)
current_superuser = fastapi_users.current_user(active=True, superuser=True)
app = FastAPI(title="FastAPI Users Example")
# 認証ルーター
app.include_router(
fastapi_users.get_auth_router(jwt_backend),
prefix="/auth/jwt",
tags=["auth"],
)
app.include_router(
fastapi_users.get_auth_router(cookie_backend),
prefix="/auth/cookie",
tags=["auth"],
)
# ユーザー登録ルーター
app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["auth"],
)
# パスワードリセットルーター
app.include_router(
fastapi_users.get_reset_password_router(),
prefix="/auth",
tags=["auth"],
)
# メール認証ルーター
app.include_router(
fastapi_users.get_verify_router(UserRead),
prefix="/auth",
tags=["auth"],
)
# ユーザー管理ルーター
app.include_router(
fastapi_users.get_users_router(UserRead, UserUpdate),
prefix="/users",
tags=["users"],
)
@app.on_event("startup")
async def on_startup():
# データベースとテーブルを作成
await create_db_and_tables()
# 保護されたルートの例
@app.get("/protected")
async def protected_route(user: User = Depends(current_active_user)):
return f"こんにちは、{user.email}さん!"
@app.get("/admin")
async def admin_route(user: User = Depends(current_superuser)):
return f"管理者 {user.email} としてログインしています。"
OAuth プロバイダーとの統合
# oauth.py - OAuth設定
from httpx_oauth.clients.google import GoogleOAuth2
google_oauth_client = GoogleOAuth2(
client_id="YOUR_GOOGLE_CLIENT_ID",
client_secret="YOUR_GOOGLE_CLIENT_SECRET",
)
# OAuth ルーターの追加 (app.py内)
app.include_router(
fastapi_users.get_oauth_router(
google_oauth_client,
jwt_backend,
SECRET,
is_verified_by_default=True, # OAuth ユーザーは自動的に認証済み
associate_by_email=True, # 同じメールアドレスのアカウントを関連付け
),
prefix="/auth/google",
tags=["auth"],
)
# 既存ユーザーがOAuthアカウントを関連付けるためのルーター
app.include_router(
fastapi_users.get_oauth_associate_router(google_oauth_client, UserRead, SECRET),
prefix="/auth/associate/google",
tags=["auth"],
)
高度なユーザー管理とカスタマイズ
# advanced_users.py - 高度なユーザー管理
import uuid
from typing import Optional
from fastapi import Request, HTTPException, status
from fastapi_users import BaseUserManager
from fastapi_users.password import PasswordHelper
from .db import User
from .schemas import UserCreate
class AdvancedUserManager(BaseUserManager[User, uuid.UUID]):
reset_password_token_secret = SECRET
verification_token_secret = SECRET
def __init__(self, user_db):
super().__init__(user_db)
# カスタムパスワードヘルパー
self.password_helper = PasswordHelper()
async def create(
self,
user_create: UserCreate,
safe: bool = False,
request: Optional[Request] = None,
) -> User:
"""カスタムユーザー作成ロジック"""
# メールドメインの制限
allowed_domains = ["company.com", "trusted-partner.com"]
email_domain = user_create.email.split("@")[1]
if email_domain not in allowed_domains:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="許可されていないメールドメインです"
)
# ユーザー作成前の処理
user = await super().create(user_create, safe, request)
# ユーザー作成後の処理(例:デフォルトロールの割り当て)
# await self.assign_default_role(user)
return user
async def on_after_register(self, user: User, request: Optional[Request] = None):
"""ユーザー登録後の処理"""
print(f"新規ユーザー登録: {user.email}")
# メール送信、ログ記録、外部システム連携など
# await self.send_welcome_email(user)
# await self.log_user_registration(user)
async def on_after_login(
self,
user: User,
request: Optional[Request] = None,
response: Optional[Response] = None,
):
"""ログイン後の処理"""
print(f"ユーザーログイン: {user.email}")
# ログイン履歴の記録、セッション管理など
# await self.update_last_login(user)
async def on_after_update(
self,
user: User,
update_dict: dict,
request: Optional[Request] = None,
):
"""ユーザー情報更新後の処理"""
print(f"ユーザー情報更新: {user.email}")
# 変更通知、監査ログなど
async def validate_password(
self,
password: str,
user: User | UserCreate,
) -> None:
"""カスタムパスワード検証"""
if len(password) < 12:
raise ValueError("パスワードは12文字以上である必要があります")
if not any(char.isupper() for char in password):
raise ValueError("パスワードには大文字を含める必要があります")
if not any(char.islower() for char in password):
raise ValueError("パスワードには小文字を含める必要があります")
if not any(char.isdigit() for char in password):
raise ValueError("パスワードには数字を含める必要があります")
if not any(char in "!@#$%^&*()_+-=[]{}|;:,.<>?" for char in password):
raise ValueError("パスワードには特殊文字を含める必要があります")
async def authenticate(
self, credentials: OAuth2PasswordRequestForm
) -> Optional[User]:
"""カスタム認証ロジック"""
try:
user = await self.get_by_email(credentials.username)
except UserNotExists:
# タイミング攻撃対策のためのダミー検証
self.password_helper.hash("dummy")
return None
# アカウントロック機能の例
if hasattr(user, 'failed_login_attempts') and user.failed_login_attempts >= 5:
raise HTTPException(
status_code=status.HTTP_423_LOCKED,
detail="アカウントがロックされています"
)
verified, updated_password_hash = self.password_helper.verify_and_update(
credentials.password, user.hashed_password
)
if not verified:
# ログイン失敗回数を増加
# await self.increment_failed_login_attempts(user)
return None
# ログイン成功時はカウンターをリセット
# await self.reset_failed_login_attempts(user)
if updated_password_hash is not None:
await self.user_db.update(user, {"hashed_password": updated_password_hash})
return user
動的認証バックエンドとセキュリティ設定
# security.py - セキュリティとアクセス制御
from fastapi import Request, Depends, HTTPException, status
from fastapi_users.authentication import AuthenticationBackend
async def get_enabled_backends(request: Request):
"""リクエストに基づいて有効な認証バックエンドを動的に決定"""
# 管理者向けエンドポイントはJWTのみ
if request.url.path.startswith("/admin"):
return [jwt_backend]
# API エンドポイントはJWTのみ
if request.url.path.startswith("/api"):
return [jwt_backend]
# その他はCookieとJWTの両方を許可
return [cookie_backend, jwt_backend]
# 動的認証の使用例
current_user_dynamic = fastapi_users.current_user(
active=True,
get_enabled_backends=get_enabled_backends
)
@app.get("/admin/dashboard")
async def admin_dashboard(user: User = Depends(current_user_dynamic)):
if not user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="管理者権限が必要です"
)
return {"message": f"管理者ダッシュボード - {user.email}"}
# ロールベースアクセス制御の実装
def require_roles(*required_roles: str):
"""指定されたロールを要求するデコレーター"""
def role_checker(user: User = Depends(current_active_user)):
# ユーザーのロールを取得(カスタム実装が必要)
user_roles = getattr(user, 'roles', [])
if not any(role in user_roles for role in required_roles):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"必要なロール: {', '.join(required_roles)}"
)
return user
return role_checker
@app.get("/editor/content")
async def editor_content(user: User = Depends(require_roles("editor", "admin"))):
return {"message": "編集者専用コンテンツ"}