La sécurisation des API REST est un enjeu critique en 2026 : les API constituent désormais la principale surface d’attaque des applications web, représentant 83 % des attaques observées selon le rapport State of the Internet d’Akamai. JWT (JSON Web Token) et OAuth 2.0 sont les deux standards de référence pour l’authentification et l’autorisation des API REST — mais leur mauvaise implémentation génère des vulnérabilités graves. Ce tutoriel complet vous guide à travers les bonnes pratiques, les pièges courants et les configurations sécurisées en Python (FastAPI) et JavaScript (Node.js/Express).

Comprendre la différence entre authentification et autorisation

L’authentification répond à la question « Qui êtes-vous ? » — elle vérifie l’identité de l’appelant. L’autorisation répond à « Que pouvez-vous faire ? » — elle vérifie les permissions associées à cette identité. Cette distinction est fondamentale : une API peut authentifier correctement un utilisateur tout en lui autorisant l’accès à des ressources qui ne lui appartiennent pas (vulnérabilité BOLA — Broken Object Level Authorization, #1 de l’OWASP API Security Top 10 2023).

Dans une API REST, l’authentification est généralement résolue par une des approches suivantes : API Keys (simples mais limitées), Basic Authentication (obsolète en 2026 pour les APIs publiques), JWT Bearer tokens ou OAuth 2.0. L’autorisation peut être basée sur des rôles (RBAC — Role-Based Access Control), sur des attributs (ABAC — Attribute-Based Access Control), ou sur des scopes OAuth. La plupart des APIs modernes combinent JWT (pour porter les claims d’identité) et OAuth 2.0 (pour le flux d’obtention des tokens).

Un piège courant est de confondre « avoir un JWT valide » et « avoir les permissions nécessaires ». Un JWT valide prouve l’identité et les claims au moment de l’émission, mais ne garantit pas que l’utilisateur a le droit d’accéder à la ressource spécifique demandée (un article, un compte bancaire, un profil utilisateur). La vérification des permissions doit être effectuée côté serveur pour chaque requête, indépendamment de la validité du JWT.

JWT (JSON Web Token) : fonctionnement et structure

Un JWT est une chaîne de caractères encodée en Base64URL composée de trois parties séparées par des points : l’en-tête (header), la charge utile (payload) et la signature. L’en-tête contient l’algorithme de signature (`{« alg »: « RS256 », « typ »: « JWT »}`). La charge utile contient les claims — des assertions sur l’utilisateur et la session : `iss` (émetteur), `sub` (sujet, l’identifiant utilisateur), `exp` (expiration en timestamp Unix), `iat` (date d’émission), et des claims personnalisés comme `role` ou `permissions`.

La signature est calculée sur la concaténation de l’en-tête et du payload encodés, en utilisant une clé secrète (HMAC-SHA256, appelé HS256) ou une paire de clés asymétriques (RSA-SHA256, appelé RS256 ou ECDSA appelé ES256). RS256 et ES256 sont recommandés pour les APIs en production : ils permettent à plusieurs services de vérifier les tokens avec la clé publique, sans que chaque service ait besoin de la clé privée. Cela évite la compromission de toute l’infrastructure si un service est piraté.

La durée de vie d’un JWT doit être courte : 15 minutes à 1 heure pour les access tokens, 7 à 30 jours pour les refresh tokens. Cette stratégie limite l’impact d’un token volé. Un access token volé ne peut être utilisé que pendant sa durée de vie courte, tandis que le refresh token permet d’en obtenir de nouveaux de manière transparente pour l’utilisateur. Attention : les JWT sont des tokens « opaque » côté serveur — une fois émis, il est impossible de les invalider sans maintenir une liste de révocation (blocklist), ce qui ajoute une couche de complexité.

OAuth 2.0 : les 4 flux et quand les utiliser

OAuth 2.0 définit 4 « grant types » (flux d’autorisation) adaptés à différents contextes. L’Authorization Code Flow est le flux standard pour les applications web et mobiles : l’utilisateur est redirigé vers le serveur d’autorisation (Auth0, Keycloak, Cognito), s’authentifie, puis l’application reçoit un code d’autorisation échangeable contre des tokens. Le PKCE (Proof Key for Code Exchange) est obligatoire pour les clients publics (SPA, applications mobiles) depuis la mise à jour de la RFC 9700 en 2025.

Le Client Credentials Flow est utilisé pour les communications machine-à-machine (M2M) : un service backend obtient un access token en s’authentifiant directement avec son client_id et client_secret, sans interaction utilisateur. C’est le flux approprié pour les APIs consommées par des microservices ou des scripts d’automatisation. L’Implicit Flow (token retourné directement dans l’URL) est désormais déprécié par l’OAuth Security BCP (Best Current Practice) et ne doit plus être utilisé en 2026.

Les scopes OAuth définissent la granularité des permissions : `read:articles`, `write:articles`, `admin:users` sont des exemples de scopes bien définis. Un client OAuth doit demander uniquement les scopes dont il a besoin (principe du moindre privilège). L’utilisateur voit les scopes demandés sur l’écran de consentement et peut les refuser individuellement. Côté serveur, chaque endpoint doit vérifier que le token présenté contient bien le scope requis — une vérification souvent oubliée dans les implémentations naïves.

Implémentation sécurisée avec FastAPI (Python)

FastAPI intègre nativement le support OAuth2 et JWT via la dépendance `python-jose` pour les opérations JWT et `passlib` pour le hachage des mots de passe. La configuration sécurisée recommande RS256 (asymétrique) plutôt que HS256 (symétrique) en production. Les clés RSA peuvent être générées avec `openssl genrsa -out private.pem 2048` et stockées dans des variables d’environnement ou un coffre-fort de secrets (HashiCorp Vault, AWS Secrets Manager).

La validation du JWT côté FastAPI doit vérifier : la signature cryptographique, la date d’expiration (`exp`), l’émetteur (`iss`), et l’audience (`aud`). Ne jamais faire confiance au contenu du JWT sans vérifier la signature — une vulnérabilité classique (CVE-2015-9235 « alg:none ») consiste à accepter un JWT dont l’algorithme est défini à « none », ne nécessitant aucune signature. Utilisez une bibliothèque bien maintenue (python-jose >= 3.3.0 ou PyJWT >= 2.8.0) et verrouillez explicitement l’algorithme attendu.

La gestion des erreurs est souvent bâclée : ne jamais retourner de messages d’erreur détaillés qui révèlent la raison de l’échec d’authentification (token expiré vs token invalide vs utilisateur inexistant). Retourner systématiquement HTTP 401 Unauthorized avec un message générique `{« detail »: « Could not validate credentials »}`. Logguer les tentatives d’authentification échouées (avec l’IP source) pour la détection des attaques par force brute, mais ne jamais logger le token JWT lui-même.

# Python FastAPI - Implementation JWT securisee avec RS256
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from datetime import datetime, timedelta
import os

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")

# Charger les cles depuis variables d'environnement (jamais en dur dans le code)
PRIVATE_KEY = os.environ["JWT_PRIVATE_KEY"]
PUBLIC_KEY = os.environ["JWT_PUBLIC_KEY"]
ALGORITHM = "RS256"   # Toujours fixer l'algorithme explicitement
ACCESS_TOKEN_EXPIRE_MINUTES = 15

def create_access_token(sub: str, scopes: list[str]) -> str:
    now = datetime.utcnow()
    payload = {
        "sub": sub,
        "iss": "https://auth.monapi.com",
        "aud": "https://api.monapi.com",
        "iat": now,
        "exp": now + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
        "scopes": scopes,
    }
    return jwt.encode(payload, PRIVATE_KEY, algorithm=ALGORITHM)

def verify_token(token: str = Depends(oauth2_scheme)) -> dict:
    try:
        payload = jwt.decode(
            token, PUBLIC_KEY,
            algorithms=[ALGORITHM],   # Liste fixe, jamais dynamique
            audience="https://api.monapi.com",
            issuer="https://auth.monapi.com",
        )
        return payload
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",  # Message generique
            headers={"WWW-Authenticate": "Bearer"},
        )

@app.get("/api/articles")
async def list_articles(token_data: dict = Depends(verify_token)):
    # Verifier le scope specifique a cet endpoint
    if "read:articles" not in token_data.get("scopes", []):
        raise HTTPException(status_code=403, detail="Insufficient scope")
    return {"articles": []}

Les 10 vulnérabilités JWT les plus courantes

L’OWASP API Security Top 10 et les analyses de PortSwigger Web Security Academy recensent les vulnérabilités JWT les plus fréquentes en 2026. Les trois premières : (1) Accepter l’algorithme « none » — l’attaquant modifie le payload et supprime la signature. Solution : rejeter tout JWT dont l’en-tête `alg` vaut « none » ou est absent. (2) Confusion RS256 / HS256 — si le serveur accepte HS256, un attaquant peut signer un JWT forgé avec la clé publique RS256 (publique, donc connue). Solution : valider explicitement l’algorithme attendu. (3) Clé HMAC trop faible — un secret de 16 caractères est crackable offline avec JohnTheRipper.

Les vulnérabilités 4 à 7 : (4) Pas de vérification de l’expiration (`exp`) — des tokens valides pour toujours. (5) JWT stocké dans localStorage — accessible via XSS. Solution : stocker les tokens dans des cookies HttpOnly + SameSite=Strict. (6) Pas de vérification de l’audience (`aud`) — un token émis pour l’API mobile peut être réutilisé sur l’API admin. (7) Claims non vérifiés — un `role: « admin »` dans le payload n’est pas une preuve, c’est une déclaration à vérifier côté serveur contre la base de données.

Les vulnérabilités 8 à 10 : (8) Refresh token sans rotation — chaque utilisation d’un refresh token devrait invalider l’ancien et en émettre un nouveau. (9) Pas de révocation en cas de compromission — maintenir une liste de révocation (Redis, base de données) pour invalider les tokens avant leur expiration naturelle. (10) JWT dans les logs ou les URL — les tokens dans l’URL (GET /api?token=xxx) apparaissent dans les logs serveur, les caches CDN et l’historique du navigateur. Toujours transmettre les tokens dans l’en-tête `Authorization: Bearer `.

Rate limiting, CORS et autres mesures de sécurité complémentaires

Le rate limiting protège l’API contre les attaques par force brute sur les endpoints d’authentification et contre les abus de ressources. En Python avec FastAPI, la bibliothèque `slowapi` (basée sur `limits`) permet de configurer des limites par IP : `@limiter.limit(« 5/minute »)` sur l’endpoint `/auth/token`. Pour les APIs en production, déporter le rate limiting au niveau du reverse proxy (Nginx) ou de l’API Gateway (Kong, AWS API Gateway) est plus performant.

La configuration CORS (Cross-Origin Resource Sharing) doit être explicite et restrictive. Ne jamais utiliser `Access-Control-Allow-Origin: *` pour des endpoints authentifiés — cela permettrait à n’importe quel site malveillant d’effectuer des requêtes cross-origin avec les credentials de l’utilisateur. Lister explicitement les origines autorisées (`https://app.mondomaine.com`), les méthodes HTTP permises et les en-têtes autorisés. Le middleware CORS doit être configuré avant toute logique d’authentification.

L’HTTPS est non négociable en 2026 : toute API REST exposant des tokens ou des données sensibles doit forcer TLS 1.2 minimum (TLS 1.3 recommandé) avec des chiffres forts (ECDHE + AES-256-GCM). Les en-têtes de sécurité HTTP sont également importants pour les APIs qui renvoient du JSON : `Strict-Transport-Security` (HSTS), `X-Content-Type-Options: nosniff`, `Content-Security-Policy` (si l’API sert du HTML). Utilisez des outils comme Mozilla Observatory ou SecurityHeaders.com pour auditer la configuration HTTP de votre API.

Utiliser un Identity Provider (IdP) plutôt que de tout implémenter

En 2026, la recommandation de l’industrie est de déléguer l’authentification à un Identity Provider (IdP) plutôt que de construire sa propre implémentation JWT/OAuth. Les solutions comme Auth0, Okta, Keycloak (open-source), ou les services cloud (AWS Cognito, Google Identity Platform, Azure AD B2C) gèrent la complexité de OAuth 2.0, la rotation des clés de signature, la révocation des tokens, la gestion du MFA et la conformité réglementaire (SOC 2, ISO 27001). Le coût en temps et en risque de sécurité d’une implémentation maison dépasse largement le coût d’un IdP.

Keycloak est particulièrement populaire pour les déploiements on-premise ou en cloud privé : il peut être déployé en Docker ou Kubernetes, supporte les protocoles OIDC (OpenID Connect), SAML 2.0 et OAuth 2.0, et offre une interface d’administration complète pour gérer les utilisateurs, les rôles et les clients. Pour les startups ou les PME sans contrainte on-premise, Auth0 (plan développeur gratuit jusqu’à 7 500 utilisateurs actifs mensuels) ou Firebase Authentication offrent une intégration rapide avec des SDK pour Python, Node.js, React et mobile.

Même en utilisant un IdP, la responsabilité de la validation côté API reste entière. L’API doit toujours vérifier la signature du JWT (avec la clé publique JWKS fournie par l’IdP), l’expiration, les scopes et les permissions spécifiques aux ressources demandées. L’IdP authentifie « l’identité », mais l’API autorise « l’accès à ses propres ressources » — ces deux responsabilités ne se délèguent pas entièrement. Une mauvaise configuration de la validation côté API annule tous les bénéfices de l’IdP.

Audit et tests de sécurité de votre API

Tester la sécurité de votre API avant de la mettre en production est indispensable. L’outil `jwt_tool` (Python, open-source sur GitHub) permet de tester automatiquement une vingtaine de vulnérabilités JWT connues : confusion d’algorithme, clé faible, claims non vérifiés, etc. OWASP ZAP et Burp Suite Pro intègrent des scanners d’API REST qui testent les endpoints contre l’OWASP API Security Top 10. Pour les tests de charge et de rate limiting, `locust` (Python) ou `k6` (JavaScript) permettent de simuler des milliers de requêtes simultanées.

Les tests de sécurité automatisés doivent être intégrés dans le pipeline CI/CD : à chaque pull request, un scanner d’API (par exemple SpectralHQ pour la validation du contrat OpenAPI, ou OWASP ZAP en mode API via Docker) vérifie les régressions de sécurité. Les outils DAST (Dynamic Application Security Testing) comme Nuclei permettent de tester les API en production dans un environnement contrôlé. La combinaison SAST (analyse statique du code) + DAST (tests dynamiques) + pentests manuels trimestriels est la approche la plus complète.

La documentation de votre API (OpenAPI/Swagger) doit inclure les schémas de sécurité : quels endpoints requièrent un JWT, quels scopes sont nécessaires, quelles erreurs peuvent être retournées. Cette documentation sert à la fois de contrat avec les consommateurs de l’API et de base pour les tests automatisés. Des outils comme Spectral permettent de valider que la spécification OpenAPI respecte les bonnes pratiques de sécurité (endpoints sans auth documentée, schémas trop permissifs, etc.) avant même que le code ne soit écrit.

Sources et références

W
WP Admin Lab

Architecte web full-stack. WordPress, performance, data et sécurité. Notes de terrain, tests reproductibles et retours d'expérience.