Avancé

Sécuriser l'API avec une clé d'API

Pour l'instant, n'importe qui connaissant l'URL peut appeler vos endpoints. Cette section ajoute une authentification par clé d'API et la gestion des secrets.

Pourquoi sécuriser ?

Votre API Flask tourne sur localhost:5000. En développement, c'est accessible uniquement sur votre machine. Mais dès que l'API est déployée (ou accessible sur un réseau local), n'importe qui peut appeler vos endpoints.

La clé d'API (API key) est le mécanisme d'authentification le plus simple : un secret partagé entre le client et le serveur, envoyé dans chaque requête.

Limites d'une clé d'API

La clé d'API n'offre pas de gestion d'expiration, de rotation automatique, ni de contrôle fin des permissions. Elle suffit pour protéger un outil interne et introduire le concept d'authentification HTTP.

Côté Flask : le décorateur @require_api_key

La clé d'API est transmise dans un header HTTP. La convention la plus répandue est X-API-Key.

Un décorateur qui vérifie la présence et la validité de la clé :

python
import os
from functools import wraps
from flask import request, jsonify

API_KEY = os.environ.get("API_KEY")  # Ne jamais hardcoder la clé !


def require_api_key(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        key = request.headers.get("X-API-Key")
        if not key or key != API_KEY:
            return jsonify({"error": "Clé API manquante ou invalide"}), 401
        return f(*args, **kwargs)
    return decorated

Appliquez-le sur vos endpoints :

python
@ventes_bp.route("/par-region")
@require_api_key
def ventes_par_region():
    # ... votre code existant
    ...

Ne jamais hardcoder la clé API

python
# JAMAIS
API_KEY = "ma-cle-secrete-12345"

# Toujours depuis l'environnement
API_KEY = os.environ.get("API_KEY")

Une clé hardcodée dans le code source finit inévitablement dans le dépôt Git — et devient publique.

401 vs 403 : quelle différence ?

Le décorateur retourne un code 401 (Unauthorized). Pourquoi 401 et pas 403 ?

CodeNomSignification
401UnauthorizedAuthentification manquante ou invalide — « qui êtes-vous ? »
403ForbiddenAuthentifié mais pas autorisé — « je sais qui vous êtes, mais vous n'avez pas le droit »

Sans clé valide, le client n'est pas authentifié : c'est un 401. Un 403 s'utiliserait si le client fournit une clé valide mais n'a pas les droits pour accéder à une ressource spécifique (par exemple, un utilisateur standard qui tente d'accéder à un endpoint admin).

Quel code HTTP doit retourner l'API quand la clé d'API est absente ou invalide ?

Côté Streamlit : envoyer la clé dans les headers

La fonction api_get doit inclure la clé d'API dans chaque requête :

python
API_KEY = st.secrets["API_KEY"]  # Depuis le fichier secrets.toml


def api_get(endpoint: str, params: dict = None) -> dict | None:
    base_url = "http://localhost:5000/api/v1"
    headers = {"X-API-Key": API_KEY}

    try:
        response = requests.get(
            f"{base_url}/{endpoint}",
            params=params,
            headers=headers,
            timeout=10
        )
        response.raise_for_status()
        return response.json()
    except requests.exceptions.HTTPError:
        if response.status_code == 401:
            st.error("Clé API invalide. Vérifiez votre configuration.")
        elif response.status_code == 404:
            st.warning("Aucune donnée trouvée.")
        else:
            st.error(f"Erreur API (code {response.status_code}).")
        return None
    except requests.exceptions.ConnectionError:
        st.error("API inaccessible.")
        return None
    except requests.exceptions.Timeout:
        st.error("L'API ne répond pas dans les délais.")
        return None
    except Exception as e:
        st.error(f"Erreur inattendue : {e}")
        return None

Le code 401 reçoit un message spécifique, distinct des autres erreurs HTTP.

Gérer les secrets côté Streamlit : st.secrets

Streamlit fournit un mécanisme de secrets intégré via .streamlit/secrets.toml :

toml
API_KEY = "ma-cle-secrete-12345"
API_BASE_URL = "http://localhost:5000/api/v1"

Utilisez-le dans votre code :

python
API_KEY = st.secrets["API_KEY"]
BASE_URL = st.secrets["API_BASE_URL"]

Ajoutez secrets.toml à .gitignore

Le fichier secrets.toml contient des secrets — il ne doit jamais être commité dans Git :

bash
# .gitignore
.streamlit/secrets.toml

Où la clé d'API doit-elle être stockée côté Streamlit ?

Gérer les secrets côté Flask : os.environ + python-dotenv

Côté Flask, utilisez les variables d'environnement avec python-dotenv :

bash
pip install python-dotenv

Créez un fichier .env à la racine du projet Flask :

bash
API_KEY=ma-cle-secrete-12345

Chargez-le dans votre application Flask :

python
from dotenv import load_dotenv
import os

load_dotenv()  # Charge les variables depuis .env

API_KEY = os.environ.get("API_KEY")

os.environ.get() avec valeur par défaut

En développement, vous pouvez fournir une valeur par défaut pour éviter les erreurs si le fichier .env n'est pas présent :

python
API_KEY = os.environ.get("API_KEY", "cle-dev-par-defaut")

En production, ne mettez jamais de valeur par défaut pour les secrets — une erreur explicite est préférable à une clé par défaut non sécurisée.

Résumé des fichiers secrets

CôtéFichierAccès dans le codeFormat
Streamlit.streamlit/secrets.tomlst.secrets['CLE']TOML
Flask.envos.environ.get('CLE')KEY=VALUE

Les deux fichiers doivent figurer dans .gitignore.

Quel outil utilise Flask pour charger les variables d'environnement depuis un fichier .env ?

À retenir

Points clés

  • Clé d'API : authentification simple via le header X-API-Key
  • 401 (Unauthorized) = pas authentifié, 403 (Forbidden) = authentifié mais pas autorisé
  • Ne jamais hardcoder les clés dans le code source
  • Streamlit : st.secrets + .streamlit/secrets.toml
  • Flask : os.environ + python-dotenv + .env
  • Les deux fichiers de secrets doivent être dans .gitignore

Ressources

L'API est sécurisée et les secrets sont gérés hors du code source. Reste à structurer le code de communication HTTP dans une classe client dédiée.