Use when sécuriser, auditer ou durcir une API. Couvre l'authentification (JWT, OAuth2, API Keys), l'autorisation (RBAC, ABAC, ACL), la validation des entrées, la protection contre les attaques (injection, CSRF, XSS, SSRF), TLS, CORS, rate limiting, logging de sécurité, et le stockage des secrets.
Use when sécuriser, auditer ou durcir une API. Couvre l'authentification (JWT, OAuth2, API Keys), l'autorisation (RBAC, ABAC, ACL), la validation des entrées, la protection contre les attaques (injection, CSRF, XSS, SSRF), TLS, CORS, rate limiting, logging de sécurité, et le stockage des secrets.
La sécurité d'une API est un problème multi-couche : chaque requête traverse le réseau, le TLS, l'authentification, l'autorisation, la validation, et le backend. Une faille dans une seule couche expose l'ensemble. Ce guide couvre les 8 piliers de la sécurité API : authentification, autorisation, validation, transport, CORS, rate limiting, audit, et gestion des secrets.
Quand l'utiliser
Avant de mettre une API en production
Lors d'un audit de sécurité d'une API existante
Pour implémenter l'authentification et l'autorisation
Pour configurer TLS, CORS, et les en-têtes de sécurité HTTP
Pour se protéger contre les attaques courantes (injection, CSRF, SSRF)
1. Authentification
JWT (JSON Web Token)
Le standard le plus courant pour les APIs REST modernes :
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "clé ultra-secrète"# À mettre dans .env, PAS dans le code
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE = timedelta(minutes=30)
REFRESH_TOKEN_EXPIRE = timedelta(days=7)
defcreate_access_token(user_id: int, roles: list[str]) -> str:
payload = {
"sub": str(user_id),
"roles": roles,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + ACCESS_TOKEN_EXPIRE,
"iss": "eva-api",
"aud": "eva-client",
"jti": secrets.token_hex(16), # ID unique pour blacklist
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
defverify_access_token(token: str) -> dict:
try:
payload = jwt.decode(
token, SECRET_KEY, algorithms=[ALGORITHM],
issuer="eva-api", audience="eva-client",
)
# Vérifier si le token n'est pas blacklistéif is_blacklisted(payload["jti"]):
raise InvalidToken("Token révoqué")
return payload
except jwt.ExpiredSignatureError:
raise InvalidToken()
jwt.InvalidTokenError:
InvalidToken()
Bonnes pratiques JWT :
Toujours vérifier iss (issuer) et aud (audience)
Utiliser jti (JWT ID) pour blacklister les tokens
Tokens courts (15-30 min) + refresh tokens longs
Ne JAMAIS mettre de secrets dans le payload (il est base64, pas chiffré)
Utiliser des algorithmes asymétriques (RS256, ES256) pour les APIs publiques
OAuth 2.0
Pour les APIs qui délèguent l'authentification à un fournisseur tiers :
# Flow Authorization Code (le plus sécurisé pour les applications web)@app.get("/auth/callback")asyncdefoauth_callback(code: str):
# 1. Échanger le code contre un token
token = await exchange_code(code)
# 2. Récupérer les infos utilisateur
user_info = await get_user_info(token["access_token"])
# 3. Créer notre propre session/token
our_token = create_access_token(user_info["id"], user_info["roles"])
return {"access_token": our_token, "token_type": "bearer"}
Flux OAuth2 :
Flow
Usage
Sécurité
Authorization Code
App web avec backend
★★★★★
Authorization Code + PKCE
SPA / Mobile
★★★★★
Client Credentials
Service-to-service
★★★★☆
Resource Owner Password
Legacy (déprécié)
★★☆☆☆
Implicit Flow
Legacy (déprécié)
★☆☆☆☆
API Keys
Pour les services machine-to-machine :
from hashlib import sha256
import secrets
API_KEY_PREFIX = "eva_"defgenerate_api_key() -> tuple[str, str]:
"""Génère une paire (clé brute, hash à stocker)."""
raw = API_KEY_PREFIX + secrets.token_urlsafe(32)
hashed = sha256(raw.encode()).hexdigest()
return raw, hashed
defverify_api_key(raw_key: str, stored_hash: str) -> bool:
return sha256(raw_key.encode()).hexdigest() == stored_hash
Règle d'or : ne jamais utiliser allow_origins=["*"] avec allow_credentials=True. Soit tout est ouvert, soit les credentials sont sécurisés.
7. Protection Contre les Attaques
SQL Injection
# MAUVAIS : concaténation
cursor.execute(f"SELECT * FROM articles WHERE id = {id}")
# BON : paramètres
cursor.execute("SELECT * FROM articles WHERE id = %s", (id,))