Comment protège-t-on une API ?
Protéger une API web, c'est vérifier qui envoie chaque requête et limiter ce qu'elle peut faire. On s'aide de clés, de jetons et d'une limite de débit. Sans cela, n'importe qui sur Internet peut lire, changer ou saturer vos données.
Trois questions se posent dans l'ordre : qui êtes-vous, avez-vous le droit, et en demandez-vous trop ?
À la fin de cette leçon
Remarque
Je peux protéger une API avec une clé, un jeton et une limite de débit.
Mots clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Authentification | prouver qui l'on est | |
| Autorisation | décider ce que quelqu'un a le droit de faire | |
| Clé d'API | un long secret qu'un programme envoie pour s'identifier | |
| Jeton porteur | un secret à durée limitée, envoyé dans l'en-tête Authorization | Bearer abc123 |
| Expiration | le moment où un jeton cesse d'être valide | |
| Limite de débit | un nombre maximal de requêtes accepté sur une période |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une clé dit qui appelle
L'authentification répond à la question « qui êtes-vous ? ». Un programme envoie une clé d'API, ou un jeton, dans un en-tête de chaque requête.
L'autorisation répond à une autre question : « avez-vous le droit de faire cela ? ». Une personne identifiée peut très bien être refusée.
Exemple : une requête avec un jeton
GET /livres HTTP/1.1
Host: api:3000
Authorization: Bearer jeton-d-exemple-123Le laboratoire est ouvert à dessein
Le serveur du laboratoire ne vérifie aucune clé. La base est une copie privée, à vous seul. Une vraie API, elle, refuse tout ce qui n'est pas identifié.
Préparez d'abord le catalogue, puis envoyez une fausse clé. Le serveur doit l'ignorer : la vérification est du code que vous écrivez.
Créer la table des livres
PGPASSWORD=shop psql -q -h db -U shop -c "set client_min_messages to warning; drop table if exists livres; create table livres (id serial primary key, titre text not null unique, auteur text not null, annee integer not null, exemplaires integer not null default 1 check (exemplaires >= 0), disponible boolean not null default true, resume text); insert into livres (titre, auteur, annee, exemplaires, disponible) values ('Germinal', 'Emile Zola', 1885, 3, true), ('Candide', 'Voltaire', 1759, 0, false), ('Madame Bovary', 'Gustave Flaubert', 1857, 2, true), ('La Peste', 'Albert Camus', 1947, 4, true), ('Bel-Ami', 'Guy de Maupassant', 1885, 1, true); notify pgrst, 'reload schema'"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lire sans aucune clé
curl -s -o /dev/null -w "%{http_code}\n" "http://api:3000/livres"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Voir l'en-tête que curl envoie
curl -s -v -o /dev/null -H "X-Cle-Api: cle-secrete-1" "http://api:3000/livres" 2>&1 | grep -E '^> X-Cle-Api'Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lire avec une fausse clé
curl -s -o /dev/null -w "%{http_code}\n" -H "X-Cle-Api: cle-secrete-1" "http://api:3000/livres"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Gardez l'empreinte de la clé, pas la clé
Un serveur n'a pas besoin de garder les clés en clair. Il garde leur empreinte, le résultat d'une fonction de hachage comme SHA-256. À chaque requête, il calcule l'empreinte de la clé reçue et la compare.
Si la liste fuit, on n'y lit pas les clés. La même clé donne toujours la même empreinte.
Calculer l'empreinte d'une clé
printf 'cle-secrete-1' | sha256sumAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Un jeton cesse de valoir à l'expiration
Un jeton porteur est émis à un instant et vaut pour une durée. Passé ce délai, le serveur le refuse et le client doit en demander un autre.
Si on vole un jeton, il ne sert que peu de temps. Le tableau suit un jeton émis à l'instant 100, valable 60 secondes.
Exemple : un jeton valable 60 secondes
| Instant | Âge du jeton | Âge inférieur à 60 ? | Verdict |
|---|---|---|---|
| 130 | 30 s | Oui | Valide |
| 159 | 59 s | Oui | Valide |
| 160 | 60 s | Non | Expiré |
| 200 | 100 s | Non | Expiré |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une limite de débit arrête l'abus
Même un client identifié peut tout saturer par erreur. Une limite de débit refuse ses requêtes en trop, avec le code 429.
Le seau à jetons est la méthode la plus courante. Le seau contient au plus 3 jetons et en regagne 1 par seconde. Chaque requête en prend un. Sans jeton, elle est refusée.
Exemple : un seau de trois jetons
| Instant | Jetons avant | Décision | Jetons après |
|---|---|---|---|
| 0 | 3 | Acceptée | 2 |
| 0 | 2 | Acceptée | 1 |
| 0 | 1 | Acceptée | 0 |
| 0 | 0 | Refusée (429) | 0 |
| 1 | 1 (un regagné) | Acceptée | 0 |
| 1 | 0 | Refusée (429) | 0 |
| 5 | 3 (quatre regagnés, plafonné à 3) | Acceptée | 2 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Exemple : le seau en Python
capacite = 3
jetons = 3
dernier = 0
# Changez la capacité en 2 et relancez : la suite change.
for instant in [0, 0, 0, 0, 1, 1, 5]:
jetons = min(capacite, jetons + (instant - dernier))
dernier = instant
if jetons >= 1:
jetons = jetons - 1
print(instant, "acceptee", jetons)
else:
print(instant, "refusee", jetons)
Une erreur fréquente
Attention
Beaucoup pensent qu'une clé placée dans le code d'une page web reste secrète. Faux : le navigateur envoie ce code à chaque visiteur, qui peut le lire. Une clé secrète vit sur un serveur, jamais dans la page.