Sécuriser une API implique deux concepts distincts :
- Authentification (AuthN) : Qui es-tu ? → vérifier l’identité du client
- Autorisation (AuthZ) : As-tu le droit ? → vérifier les permissions
Pour le chiffrement du transport, voir TLS et SSL (HTTPS obligatoire pour toute API).
Vue d’ensemble des mécanismes
| Mécanisme | Complexité | Idéal pour | Stateless |
|---|---|---|---|
| API Key | Faible | APIs publiques simples, M2M | ✅ |
| Basic Auth | Faible | Outils internes, simple | ✅ |
| JWT | Moyenne | Microservices, APIs stateless | ✅ |
| OAuth2 | Élevée | Accès délégué (tierce partie) | ✅ |
| OpenID Connect | Élevée | Authentification utilisateur + OAuth2 | ✅ |
| mTLS | Élevée | Services internes, Zero Trust | ✅ |
| Session Cookie | Faible | Apps web traditionnelles | ❌ |
API Key
La méthode la plus simple : un token opaque généré par le serveur, transmis par le client à chaque requête.
# Dans un header (recommandé)
curl https://api.exemple.com/data \
-H "X-API-Key: sk_live_abc123xyz"
# Dans un query param (déconseillé — apparaît dans les logs)
curl "https://api.exemple.com/data?api_key=sk_live_abc123xyz"Avantages : simple à implémenter, simple à révoquer
Inconvénients : pas d’expiration native, pas d’identité utilisateur, vol difficile à détecter
# Vérification côté serveur
def verify_api_key(request):
key = request.headers.get("X-API-Key")
if not key or not db.api_keys.exists(key):
raise HTTPException(401, "Invalid API Key")
return db.api_keys.get_owner(key)JWT — JSON Web Token
Un JWT est un token auto-portant : il contient lui-même les informations d’identité et de droits, signées par le serveur. Le serveur n’a pas besoin de le stocker.
Structure d’un JWT
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 ← Header (base64url)
.
eyJ1c2VySWQiOiI0MiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc0NDAzMDAwMH0 ← Payload (base64url)
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature (HMAC ou RSA)
Header :
{ "alg": "RS256", "typ": "JWT" }Payload (claims) :
{
"sub": "usr_42", // subject (identifiant utilisateur)
"name": "Alice Dupont",
"role": "admin",
"iss": "auth.exemple.com", // issuer
"aud": "api.exemple.com", // audience
"iat": 1744020000, // issued at (timestamp)
"exp": 1744030000 // expiration (timestamp)
}Signature :
HMACSHA256(base64url(header) + "." + base64url(payload), secret)
// ou RSA256 avec clé privée/publique
Utilisation
# Le client envoie le JWT dans le header Authorization
curl https://api.exemple.com/users \
-H "Authorization: Bearer eyJhbGci..."# Vérification côté serveur (Python / PyJWT)
import jwt
def verify_jwt(token: str):
try:
payload = jwt.decode(
token,
public_key, # clé publique (RS256) ou secret (HS256)
algorithms=["RS256"],
audience="api.exemple.com"
)
return payload # { "sub": "usr_42", "role": "admin", ... }
except jwt.ExpiredSignatureError:
raise HTTPException(401, "Token expiré")
except jwt.InvalidTokenError:
raise HTTPException(401, "Token invalide")JWT — points d’attention
✅ Avantages ⚠️ Pièges courants
────────────────────────── ──────────────────────────────────
Stateless (pas de DB pour valider) Impossible de révoquer avant expiration
Contient les claims (pas de lookup) Ne pas mettre de données sensibles dans le payload
Signé → intégrité garantie Utiliser RS256 (asymétrique) > HS256 (symétrique)
Interopérable Vérifier exp, iss, aud obligatoirement
OAuth2 — accès délégué
OAuth2 n’est pas un protocole d’authentification mais d’autorisation déléguée : il permet à une application d’accéder à des ressources d’un utilisateur sur un autre service, sans que l’utilisateur partage son mot de passe.
Acteurs
Resource Owner = l'utilisateur (Alice)
Client = l'application tierce (ex: une app qui veut accéder à Google Drive)
Authorization Server = le serveur qui délivre les tokens (ex: accounts.google.com)
Resource Server = l'API protégée (ex: drive.googleapis.com)
Les 4 flows OAuth2
1. Authorization Code (le plus sécurisé — web apps)
Alice (navigateur) App Google
│ │ │
│── Clic "Login Google" ►│ │
│ │── GET /authorize ──────►│
│◄── Redirect vers Google │
│ │
│── Alice s'authentifie chez Google │
│── Alice autorise l'accès │
│ │
│◄── Redirect vers app avec ?code=abc ────────────│
│ │ │
│ │── POST /token ─────────►│
│ │ code=abc │
│ │◄── access_token + refresh_token
│ │ │
│ │── GET /drive/files ─────────► Resource Server
│ │ Authorization: Bearer access_token
2. Client Credentials (M2M — machine à machine)
# Utilisé quand il n'y a pas d'utilisateur humain
import requests
response = requests.post("https://auth.exemple.com/oauth/token", data={
"grant_type": "client_credentials",
"client_id": "my_service_id",
"client_secret": "my_service_secret",
"scope": "read:users write:orders"
})
token = response.json()["access_token"]
# Utiliser token pour appeler l'API3. Device Code (appareils sans navigateur)
TV / CLI Auth Server Utilisateur (téléphone)
│── POST /device ──►│ │
│◄── device_code, │ │
│ user_code: ABCD │ │
│ │ │
│ Affiche: │ │
│ "Aller sur │ │
│ exemple.com/device│ │
│ et entrer ABCD" │ │
│ │◄── utilisateur entre ABCD ──│
│── poll /token ─────►│ (en attente) │
│── poll /token ─────►│ (en attente) │
│── poll /token ─────►│── access_token ─────────────│
4. PKCE — Authorization Code pour SPAs et apps mobile
Extension de Authorization Code qui remplace le client_secret (impossible à garder secret dans un navigateur) par un code_verifier / code_challenge.
OpenID Connect (OIDC)
OIDC est une couche d’authentification construite sur OAuth2. Il ajoute un ID Token (JWT) qui contient l’identité de l’utilisateur.
OAuth2 → "Alice autorise l'accès à son Drive"
OIDC → "Alice est bien Alice" + "voici ses infos de profil"
// ID Token OIDC (décodé)
{
"iss": "https://accounts.google.com",
"sub": "1234567890", // identifiant unique Google
"email": "alice@gmail.com",
"name": "Alice Dupont",
"picture": "https://...",
"email_verified": true,
"iat": 1744020000,
"exp": 1744030000,
"aud": "mon_app_client_id"
}# Vérifier un ID Token OIDC
from jose import jwt
# Récupérer les clés publiques JWKS du provider
jwks = requests.get("https://accounts.google.com/.well-known/jwks.json").json()
payload = jwt.decode(
id_token,
jwks,
algorithms=["RS256"],
audience="mon_app_client_id"
)
user_id = payload["sub"]
email = payload["email"]mTLS — authentification par certificat
Pour les communications service-à-service (sans utilisateur humain), mTLS offre la sécurité la plus forte :
Service A ──── présente son certificat X.509 ────► Service B
Service A ◄─── présente son certificat X.509 ──── Service B
[les deux identités sont vérifiées mutuellement]
Voir TLS et SSL pour le détail du handshake mTLS.
Bonnes pratiques de sécurité API
✅ À faire ❌ À éviter
────────────────────────────────── ──────────────────────────────────
HTTPS partout (TLS 1.2+) HTTP en production
Expiration courte des tokens (15 min) Tokens sans expiration
Refresh tokens avec rotation Stocker les secrets en clair
Valider iss, aud, exp dans les JWT Faire confiance au payload sans vérifier la signature
Rate limiting par clé / utilisateur Pas de rate limiting
Scope minimal (least privilege) Tokens avec tous les droits
Logs des accès (sans données sensibles) Logger les tokens
Révoquer les clés compromises immédiatement Une seule clé pour tout
En relation avec
- API — Vue d’ensemble — comparatif des types d’API
- API REST — endpoints REST à sécuriser
- TLS et SSL — HTTPS et mTLS obligatoires
- Requêtes HTTP et HTTPS — header
Authorization, cookies