FastAPI : auth JWT et tests

🏷️ Back-end 📅 04/04/2026 09:00:00 👤 Mezgani said
Python Fastapi Jwt Tests
FastAPI : auth JWT et tests

Configurer l'authentification JWT et les tests d'intégration sur FastAPI avec une base claire pour un vrai projet backend.

Objectif de l'article

Configurer l'authentification JWT et les tests d'intégration sur FastAPI avec une base claire pour un vrai projet backend.

A retenir: JWT (JSON Web Tokens) est le standard moderne pour l'authentification sans état. Combiné avec FastAPI et une suite de tests solide, vous créez une API sécurisée et maintenable en production.

Concepts clés

Avant de coder, comprenez ces concepts fondamentaux pour FastAPI et JWT :

  • JWT (JSON Web Token) : Token stateless composé de 3 parties (header.payload.signature)
  • Bearer Token : Format standard pour transporter le JWT dans l'en-tête Authorization
  • Refresh Token : Token de longue durée pour renouveler l'accès sans re-login
  • FastAPI : Framework moderne et rapide basé sur Starlette et Pydantic
  • Scopes : Permissions pour contrôler les accès (admin, user, read, write)
  • Tests d'intégration : Tests end-to-end qui vérifient le flux complet
  • Pytest : Framework de test Python pour automatiser les tests

Implémentation

Étape 1 : Installation des dépendances

pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] pytest httpx python-multipart

Étape 2 : Créer les modèles Pydantic

# models.py
from pydantic import BaseModel
from typing import Optional

class TokenResponse(BaseModel):
    access_token: str
    refresh_token: Optional[str] = None
    token_type: str = "bearer"

class UserCreate(BaseModel):
    email: str
    password: str
    username: str

class UserResponse(BaseModel):
    id: int
    email: str
    username: str

    class Config:
        from_attributes = True

class TokenPayload(BaseModel):
    sub: str  # subject (user id)
    scopes: list = []
    exp: int  # expiration time

Étape 3 : Configurer JWT et sécurité

# security.py
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthCredentials
import os

SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-in-prod")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

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

def hash_password(password: str) > str:
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) > bool:
    return pwd_context.verify(plain_password, hashed_password)

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(credentials: HTTPAuthCredentials = Depends(security)):
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id: str = payload.get("sub")
        if user_id is None:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    except JWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return {"user_id": user_id}

Étape 4 : Routes d'authentification

# auth.py
from fastapi import APIRouter, HTTPException, Depends
from security import hash_password, verify_password, create_access_token, get_current_user

router = APIRouter(prefix="/auth", tags=["auth"])

@router.post("/register", response_model=UserResponse)
async def register(user_data: UserCreate):
    hashed_password = hash_password(user_data.password)
    # Sauvegarder en BD
    return {"id": 1, "email": user_data.email, "username": user_data.username}

@router.post("/login", response_model=TokenResponse)
async def login(email: str, password: str):
    # Vérifier utilisateur en BD
    access_token = create_access_token(data={"sub": "user_id"})
    return {
        "access_token": access_token,
        "token_type": "bearer"
    }

@router.get("/me", response_model=UserResponse)
async def get_me(current_user = Depends(get_current_user)):
    # Récupérer utilisateur depuis BD
    return {"id": 1, "email": "user@example.com", "username": "user"}

Étape 5 : Application principale

# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from auth import router as auth_router

app = FastAPI(title="API FastAPI JWT")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(auth_router)

@app.get("/health")
async def health():
    return {"status": "ok"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Tests d'intégration

Configuration de Pytest

# conftest.py
import pytest
from fastapi.testclient import TestClient
from main import app

@pytest.fixture
def client():
    return TestClient(app)

@pytest.fixture
def test_user_data():
    return {
        "email": "test@example.com",
        "password": "testpass123",
        "username": "testuser"
    }

Tests du flux d'authentification

# test_auth.py
import pytest

def test_register_success(client, test_user_data):
    response = client.post("/auth/register", json=test_user_data)
    assert response.status_code == 200
    assert response.json()["email"] == test_user_data["email"]

def test_login_success(client, test_user_data):
    client.post("/auth/register", json=test_user_data)
    response = client.post(
        "/auth/login",
        params={
            "email": test_user_data["email"],
            "password": test_user_data["password"]
        }
    )
    assert response.status_code == 200
    assert "access_token" in response.json()

def test_login_invalid_credentials(client):
    response = client.post(
        "/auth/login",
        params={"email": "wrong@example.com", "password": "wrongpass"}
    )
    assert response.status_code == 401

def test_get_current_user(client, test_user_data):
    client.post("/auth/register", json=test_user_data)
    login_response = client.post(
        "/auth/login",
        params={
            "email": test_user_data["email"],
            "password": test_user_data["password"]
        }
    )
    token = login_response.json()["access_token"]

    response = client.get(
        "/auth/me",
        headers={"Authorization": f"Bearer {token}"}
    )
    assert response.status_code == 200

def test_unauthorized_access(client):
    response = client.get("/auth/me", headers={"Authorization": "Bearer invalid_token"})
    assert response.status_code == 401

Lancer les tests

pytest -v
pytest --cov=.
pytest -k "test_login" -v

Intégration en production

Bonnes pratiques de sécurité

  • SECRET_KEY : Clé forte et unique en production (variables d'env)
  • HTTPS obligatoire : Les tokens doivent transiter en HTTPS
  • Token short-lived : Access tokens de courte durée (15-30 min)
  • CORS restrictif : Ne pas accepter toutes les origines
  • Rate limiting : Limiter les tentatives de login
  • Logging : Enregistrer les tentatives suspectes

Déploiement avec Gunicorn

pip install gunicorn

gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Configuration Nginx

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Variables d'environnement (.env)

SECRET_KEY=your-very-secret-key-change-this
DATABASE_URL=postgresql://user:password@localhost/dbname
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30

Checklist de production

  • ✅ SECRET_KEY fort et unique
  • ✅ HTTPS configuré
  • ✅ Rate limiting activé
  • ✅ Logging et monitoring
  • ✅ Tests passent 100%
  • ✅ CORS restrictif
  • ✅ Headers de sécurité
  • ✅ Backups réguliers
  • ✅ Monitoring des erreurs

Prochaines étapes

  • Ajouter OAuth2 (Google, GitHub)
  • Implémenter 2FA
  • Ajouter des logs d'audit
  • Mettre en place un blacklist de tokens
  • Intégrer un monitoring (Prometheus, Grafana)
  • Ajouter des tests de performance