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écanismeComplexitéIdéal pourStateless
API KeyFaibleAPIs publiques simples, M2M
Basic AuthFaibleOutils internes, simple
JWTMoyenneMicroservices, APIs stateless
OAuth2ÉlevéeAccès délégué (tierce partie)
OpenID ConnectÉlevéeAuthentification utilisateur + OAuth2
mTLSÉlevéeServices internes, Zero Trust
Session CookieFaibleApps 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'API

3. 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