Tous les articles

2 min de lecture

Trois garde-fous pour une API Flask en production

Validation, limitation de débit et en-têtes de sécurité : le minimum avant d’exposer un formulaire public.

  • Flask
  • Python
  • Sécurité

Le problème

Une API Flask qui fonctionne en local n’est pas une API prête pour Internet. Un formulaire de contact public attire les robots, les charges utiles malformées et les tentatives d’injection. Trois garde-fous suffisent à couvrir l’essentiel.

1. Valider avant de traiter

La validation HTML côté navigateur est une commodité, pas une sécurité. Le serveur doit revalider chaque champ, avec des limites explicites.

from flask import request

MAX_MESSAGE = 5_000

def read_contact_payload() -> dict[str, str]:
    payload = request.get_json(silent=True) or {}
    name = str(payload.get("name", "")).strip()
    email = str(payload.get("email", "")).strip()
    message = str(payload.get("message", "")).strip()

    if not name or len(name) > 120:
        raise ValueError("invalid_name")
    if "@" not in email or len(email) > 254:
        raise ValueError("invalid_email")
    if not message or len(message) > MAX_MESSAGE:
        raise ValueError("invalid_message")

    return {"name": name, "email": email, "message": message}

Le honeypot complète la validation : un champ caché que seuls les robots remplissent. S’il est rempli, la requête est rejetée sans explication.

2. Limiter le débit

Sans limitation, un script peut envoyer des milliers de messages ou saturer le serveur SMTP. Flask-Limiter s’applique par adresse IP et par route.

from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(get_remote_address, app=app, default_limits=["120 per hour"])

@app.post("/api/contact")
@limiter.limit("5 per hour")
def contact():
    ...

La limite doit être assez basse pour protéger, assez haute pour ne pas bloquer un utilisateur légitime qui corrige une faute de frappe.

3. Poser les en-têtes au bon endroit

Les en-têtes de sécurité peuvent être ajoutés par le proxy, mais l’API doit rester correcte même si elle est appelée directement. Un after_request unique évite les oublis.

@app.after_request
def security_headers(response):
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    response.headers["Cache-Control"] = "no-store"
    return response

Et les erreurs ?

Une API publique doit répondre en JSON, même quand tout va mal. Un gestionnaire d’erreurs global transforme les exceptions en réponses cohérentes ({"error": "..."}) avec le bon code HTTP, sans jamais divulguer la trace interne.

Ces garde-fous ne rendent pas une application invulnérable, mais ils éliminent les erreurs les plus fréquentes — et ils se testent, ce qui compte autant que le code lui-même.