Гайды

Аутентификация JWT и OAuth2 в FastAPI

OAuth2PasswordBearer, эндпоинт /token, PyJWT, passlib, scopes, внешний IdP и практики безопасности.

~12 мин чтения

Аутентификация JWT и OAuth2 в FastAPI

FastAPI предоставляет OAuth2PasswordBearer и утилиты для схемы password flow (логин/пароль → токен) — удобно для SPA + собственный backend. Для соцлогинов используют OAuth2 authorization code с редиректом на провайдера (отдельная интеграция). Зависимости — Маршрутизация и Dependency Injection.


1. Схема: access + refresh (рекомендация)

  • Access JWT — короткий TTL (5–15 мин), передаётся в Authorization: Bearer.
  • Refresh — httpOnly cookie или отдельный эндпоинт с ротацией; длинный TTL, хранится в БД с возможностью отзыва.

Ниже упрощённо только access; в проде добавьте refresh и blacklist/revocation по jti.


2. Зависимость OAuth2PasswordBearer

python
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")

def decode_token(token: Annotated[str, Depends(oauth2_scheme)]) -> dict:
    try:
        payload = verify_jwt(token)  # ваша функция (issuer, audience, exp)
        return payload
    except Exception:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
            headers={"WWW-Authenticate": "Bearer"},
        )

CurrentUser = Annotated[dict, Depends(decode_token)]

@app.get("/protected")
def protected(user: CurrentUser):
    return {"sub": user.get("sub")}

tokenUrl — путь, который Swagger покажет для получения токена (должен совпадать с реальным эндпоинтом).


3. Эндпоинт /token (password flow)

python
from fastapi import APIRouter
from fastapi.security import OAuth2PasswordRequestForm

router = APIRouter()

@router.post("/token")
def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(401, "Incorrect username or password")
    token = create_access_token({"sub": str(user.id), "scopes": form_data.scopes})
    return {"access_token": token, "token_type": "bearer"}

OAuth2PasswordRequestForm — поля username, password (form-urlencoded), опционально scope.


4. JWT: создание и проверка

Используйте python-jose или PyJWT с явным алгоритмом (RS256 в проде с ключами из vault; HS256 только если один доверенный issuer).

Проверяйте exp, iat, при необходимости iss, aud, nbf.

python
import jwt
from datetime import datetime, timedelta, UTC

SECRET = "change-me"  # из settings
ALGO = "HS256"

def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
    to_encode = data.copy()
    expire = datetime.now(UTC) + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET, algorithm=ALGO)

def verify_jwt(token: str) -> dict:
    return jwt.decode(token, SECRET, algorithms=[ALGO])

5. Хеш пароля

passlib с bcrypt или argon2:

python
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

def hash_password(plain: str) -> str:
    return pwd_context.hash(plain)

6. Scopes

В токен кладут scope строкой ("items:read items:write"). В FastAPI — SecurityScopes и зависимость, проверяющая пересечение с правами пользователя.


7. OAuth2 с внешним IdP (Google и т.д.)

Поток: редирект → callback с code → обмен code на токены у провайдера → создание своей сессии/JWT. Обычно библиотеки httpx-oauth / authlib; не храните client_secret в фронтенде.


8. Безопасность

  • HTTPS обязателен для bearer в заголовке.
  • CORS не разрешайте * с credentials — см. CORS и лимиты.
  • Ограничьте sub и права минимально необходимыми.
  • Ротация секретов и ключей для RS256.

Связанные материалы


Дальше: Быстрый старт · тег FastAPI