Durcir une API FastAPI face à l'OWASP Top 10
Validation stricte, JWT de courte durée, contrôle d'accès par rôle et limites de débit : les choix qui ont permis à GesApro d'obtenir 95/100 à son audit de sécurité.
GesApro gère les bons de commande et les factures d'une institution : des données financières, six rôles administratifs et un circuit de validation hiérarchique. La sécurité n'était pas une option à ajouter à la fin, elle a guidé l'architecture dès le premier endpoint. Voici les mesures qui comptent le plus, rangées selon l'OWASP Top 10.
1. Ne jamais faire confiance à l'entrée (injections)
Chaque corps de requête passe par un schéma Pydantic strict : types précis, longueurs bornées et refus des champs inconnus. Les accès base passent par l'ORM et des requêtes paramétrées, jamais par de la concaténation de chaînes.
from decimal import Decimal
from pydantic import BaseModel, ConfigDict, Field
class PurchaseOrderIn(BaseModel):
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
supplier_id: int = Field(gt=0)
label: str = Field(min_length=3, max_length=200)
amount: Decimal = Field(gt=0, max_digits=14, decimal_places=2)
currency: str = Field(pattern="^(XOF|EUR|USD|GBP)$")2. Contrôle d'accès : une dépendance par rôle
Le contrôle d'accès cassé est le premier risque de l'OWASP. Dans FastAPI, je l'exprime avec une dépendance réutilisable : un endpoint déclare les rôles qu'il accepte, et la vérification ne peut pas être oubliée dans le corps de la fonction.
from fastapi import Depends, HTTPException, status
def require_roles(*allowed: str):
def checker(user: User = Depends(get_current_user)) -> User:
if user.role not in allowed:
raise HTTPException(status.HTTP_403_FORBIDDEN, "Accès refusé")
return user
return checker
@router.post("/orders/{order_id}/approve")
def approve_order(
order_id: int,
user: User = Depends(require_roles("DG", "DFC")),
):
...3. Authentification
- Mots de passe hachés avec bcrypt, jamais stockés ni journalisés en clair.
- JWT d'accès de courte durée, renouvelé par un jeton de rafraîchissement.
- Limitation du nombre de tentatives de connexion par IP pour freiner la force brute.
4. Le reste du périmètre
- CORS limité aux domaines du frontend, en-têtes de sécurité sur chaque réponse.
- Messages d'erreur génériques côté client, détails uniquement dans les logs serveur.
- Journal d'audit horodaté de chaque action sur un bon de commande : qui, quoi, quand.