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é :
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 :
@ventes_bp.route("/par-region")
@require_api_key
def ventes_par_region():
# ... votre code existant
...
Ne jamais hardcoder la clé API
# 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 ?
| Code | Nom | Signification |
|---|---|---|
| 401 | Unauthorized | Authentification manquante ou invalide — « qui êtes-vous ? » |
| 403 | Forbidden | Authentifié 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 :
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 :
API_KEY = "ma-cle-secrete-12345"
API_BASE_URL = "http://localhost:5000/api/v1"
Utilisez-le dans votre code :
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 :
# .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 :
pip install python-dotenv
Créez un fichier .env à la racine du projet Flask :
API_KEY=ma-cle-secrete-12345
Chargez-le dans votre application Flask :
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 :
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é | Fichier | Accès dans le code | Format |
|---|---|---|---|
| Streamlit | .streamlit/secrets.toml | st.secrets['CLE'] | TOML |
| Flask | .env | os.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
Streamlit Secrets Management
Documentation officielle de st.secrets
Documentationpython-dotenv — Documentation
Guide d'utilisation de python-dotenv pour les variables d'environnement
DocumentationL'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.