Une API, c'est une porte d'entrée vers un système.
Et comme toute porte, elle peut être forcée si elle n'est pas correctement protégée.
La différence avec une interface web classique, c'est qu'une API ne cache rien derrière un joli écran.
Elle expose directement des données et des actions, sans bouton à cliquer, sans formulaire à remplir.
Un attaquant n'a pas besoin de "pirater" quoi que ce soit, il lui suffit d'envoyer des requêtes
et d'observer ce qui répond.
La sécurité d'une API repose sur trois questions fondamentales. Qui es-tu (authentification) ?
As-tu le droit (autorisation) ? Comment se protéger des abus (protection) ?
Authentification : prouver son identité
L'authentification, c'est vérifier que la personne (ou le système) qui appelle l'API
est bien celle qu'elle prétend être.
La méthode la plus simple est la clé d'API, un long code unique que le serveur
te fournit lors de ton inscription. Tu l'envoies avec chaque requête pour prouver ton identité.
C'est l'équivalent d'un badge d'accès, simple et efficace, mais si quelqu'un le vole,
il peut se faire passer pour toi.
D'où une règle qu'on ne répétera jamais assez. Une clé d'API ne se met jamais dans le code
d'une application mobile ou dans le JavaScript d'un site web, puisque n'importe qui peut
l'y lire en trente secondes. Elle vit côté serveur, dans une variable d'environnement
ou un coffre-fort à secrets, et surtout pas dans un dépôt Git.
Des robots scannent en permanence les dépôts publics à la recherche de clés oubliées,
et une clé cloud qui fuite se transforme en quelques heures en facture à cinq chiffres.
Si une clé fuite, la seule bonne réaction est de la révoquer et d'en générer une nouvelle. Supprimer le commit fautif ne suffit pas, la clé a déjà été copiée.
JWT (JSON Web Token)
Le JWT est un jeton signé qui contient des informations sur l'utilisateur,
son identifiant, ses droits, sa date d'expiration.
Le principe est simple. Tu envoies ton identifiant et ton mot de passe une seule fois.
Le serveur vérifie, puis te retourne un jeton signé. Pour les requêtes suivantes,
tu envoies juste ce jeton. Le serveur peut vérifier sa validité sans avoir besoin
de consulter sa base de données à chaque fois, car la signature prouve
qu'il a lui-même émis ce jeton et que personne ne l'a modifié.
Attention à un contresens fréquent. Un JWT est signé, pas chiffré. Son contenu est simplement
encodé en base64 et n'importe qui peut le lire en le collant dans un décodeur en ligne.
La signature garantit l'intégrité (personne n'a changé le contenu), pas la confidentialité.
On n'y met donc jamais d'information sensible.
{
"sub": "42",
"name": "Marie Dupont",
"role": "admin",
"exp": 1893456000
}C'est rapide et efficace. L'inconvénient, c'est qu'une fois émis, un JWT reste valide jusqu'à son expiration. Si un jeton est compromis, il est difficile de le révoquer immédiatement, puisque le serveur ne tient pas de liste des jetons en circulation. C'est pourquoi les jetons d'accès ont des durées de vie courtes, typiquement de quelques minutes à une heure, et sont accompagnés d'un jeton de renouvellement (refresh token) à durée plus longue, lui bien stocké côté serveur et révocable.
OAuth 2.0
OAuth résout un problème différent, permettre à une application tierce d'accéder
à tes données sans lui donner ton mot de passe.
Quand tu cliques "Se connecter avec Google" sur un site, tu es redirigé vers Google
qui te demande "Ce site veut accéder à votre nom et votre email. Acceptez-vous ?"
Si tu acceptes, Google donne au site un jeton d'accès limité. Le site peut lire
ton nom et ton email, mais il n'a jamais eu ton mot de passe Google
et ne peut pas lire tes emails.
C'est le principe de la délégation d'autorisation. OAuth 2.0 est devenu le standard
de toutes les intégrations entre services, Slack, GitHub, Facebook, Stripe.
Chaque fois qu'une application te demande "Autoriser l'accès", c'est OAuth derrière.
Petite précision de vocabulaire, souvent confondue en réunion. OAuth 2.0 gère l'autorisation,
pas l'identité. La couche qui sert à dire "voici qui est cet utilisateur" s'appelle
OpenID Connect, et elle est construite par-dessus OAuth. C'est elle qui alimente
la fameuse connexion via Google ou Microsoft. Le sujet est développé dans le chapitre
sur l'authentification.
Autorisation : vérifier les droits
L'authentification dit "je sais qui tu es". L'autorisation dit "voici ce que tu as le droit de faire".
Un utilisateur standard peut consulter ses propres commandes.
Un administrateur peut consulter toutes les commandes.
Un partenaire externe ne peut accéder qu'aux données qu'on a choisi de lui exposer.
Exemple :
Tu es connecté sur un site marchand et tu consultes ta commande à l'adresse /commandes/42.
Par curiosité, tu remplaces 42 par 43 dans la barre d'adresse.
Si le serveur se contente de vérifier que tu es connecté et te renvoie la commande n°43,
tu viens de lire l'adresse et le panier de quelqu'un d'autre. Il aurait fallu vérifier
que la commande 43 t'appartient bien.
Cette faille porte un nom, la référence directe non sécurisée à un objet, et c'est de loin
la plus fréquente sur les API. Elle trône en tête du classement OWASP des risques
propres aux API, publié par une fondation de référence en sécurité applicative.
Elle est redoutable parce qu'elle ne demande aucune compétence technique,
juste de changer un chiffre dans une URL.
Deux réflexes limitent les dégâts. D'abord, utiliser des identifiants non devinables
comme des UUID plutôt que des compteurs, ce qui complique
l'exploration à l'aveugle sans jamais remplacer le contrôle des droits.
Ensuite, filtrer systématiquement les données par le propriétaire au moment de la requête
en base, plutôt que de les récupérer puis de vérifier après coup.
Autre variante, l'exposition excessive de données. L'API renvoie l'objet utilisateur complet,
avec le mot de passe chiffré, l'email de secours et le score interne d'anti-fraude,
en se disant que le front-end n'affichera que le nom. Sauf que la réponse complète circule
et que n'importe qui peut la lire. Une API ne renvoie que ce qui est nécessaire.
Se protéger des abus
Même avec une authentification et une autorisation parfaites, une API reste vulnérable à certains abus.
Le rate limiting
Un utilisateur légitime peut envoyer des centaines de requêtes par seconde,
volontairement ou par erreur (un bug dans son code).
Le rate limiting fixe un nombre maximum de requêtes par période,
par exemple 100 requêtes par minute. Au-delà, l'API répond avec un
code HTTP 429 ("Too Many Requests")
et demande au client de ralentir.
Les API bien conçues accompagnent leur réponse d'un en-tête Retry-After
qui indique combien de secondes attendre, et de compteurs qui annoncent le quota restant.
Le client peut ainsi s'adapter au lieu de taper dans le mur en boucle.
Sans rate limiting, un seul client pourrait saturer le serveur et empêcher
tous les autres de l'utiliser. C'est ce qu'on appelle un déni de service.
Le rate limiting protège aussi contre les attaques par force brute,
où un robot teste des milliers de mots de passe sur la page de connexion.
HTTPS obligatoire
Comme nous l'avons vu dans le chapitre sur le chiffrement,
HTTPS chiffre les communications entre le client et le serveur. Sans HTTPS, les données circulent
en clair sur le réseau, identifiants, jetons, données personnelles.
N'importe qui sur le même réseau Wi-Fi pourrait les intercepter.
Aujourd'hui, une API sans HTTPS est inacceptable. C'est la base absolue de la sécurité.
Petit corollaire souvent oublié, les clés et les jetons ne se passent jamais dans l'URL,
même en HTTPS, car les URL finissent dans les journaux des serveurs, dans l'historique
du navigateur et dans les outils de monitoring. Ils voyagent dans un en-tête de la requête.
La validation des entrées
Toute donnée envoyée par un client doit être considérée comme potentiellement dangereuse.
Un champ "nom" pourrait contenir du code malveillant au lieu d'un vrai nom.
Un identifiant numérique pourrait être remplacé par du texte pour faire planter le système.
Un champ "quantité" pourrait valoir -5 pour transformer un achat en remboursement.
La validation des entrées consiste à vérifier que chaque donnée reçue correspond
à ce qui est attendu, bon type, bonne longueur, bon format, valeurs plausibles.
C'est la première ligne de défense contre les attaques par injection,
qui consistent à glisser des instructions dans un champ de saisie pour que le serveur
les exécute. L'injection SQL en est l'exemple le plus célèbre,
et elle fait des victimes depuis plus de vingt ans.
Il faut aussi borner la taille de ce qu'on accepte. Sans limite, un client peut envoyer
un fichier JSON de 500 mégaoctets ou demander 10 millions de résultats d'un coup,
et faire tomber le service sans la moindre malveillance sophistiquée.
Les secrets et les webhooks
Quand une API t'envoie des notifications automatiques (les webhooks vus dans le chapitre
sur la communication asynchrone), la question se retourne.
Ce n'est plus toi qui appelles, c'est toi qui reçois. Comment savoir que le message
"le paiement de 3 000 euros est validé" vient bien de ton prestataire et pas d'un plaisantin
qui a deviné l'adresse de ton webhook ?
La réponse passe par une signature. Le fournisseur calcule une empreinte du message
avec un secret partagé et la joint dans un en-tête. À la réception, tu recalcules
la même empreinte et tu compares. Si ça ne correspond pas, tu jettes le message.
C'est le même principe que le hachage appliqué à l'authenticité
d'un message.
API publique vs API privée
Le niveau de sécurité dépend aussi de qui peut accéder à l'API.
Une API privée n'est accessible que depuis l'infrastructure interne de l'entreprise.
Le front-end du site appelle l'API du back-end, les deux tournent sur les mêmes serveurs.
La surface d'attaque est limitée, mais la sécurité reste nécessaire.
L'idée qu'un réseau interne est sûr par nature a fait beaucoup de dégâts, au point qu'une
approche entière s'est construite contre elle, le "zero trust", où chaque appel doit
prouver son identité même à l'intérieur de la maison.
Une API publique est ouverte au monde entier. N'importe qui peut s'inscrire
et commencer à l'utiliser. Les API de Google Maps, de Stripe ou de GitHub sont publiques.
Elles nécessitent une sécurité renforcée, authentification robuste, rate limiting strict,
monitoring en temps réel, documentation des bonnes pratiques.
Une API publique doit aussi soigner ses messages d'erreur. Répondre "mot de passe incorrect"
revient à confirmer que l'email existe dans la base, ce qui aide un attaquant à constituer
sa liste de cibles. Un message générique protège mieux. De la même façon, une erreur serveur
ne doit jamais renvoyer la trace technique complète au client, qui y lirait la version
du framework et le chemin des fichiers.
La règle d'or : ne jamais faire confiance au client. Même sur une API privée, le serveur doit toujours vérifier les données et les droits. Le front-end peut être modifié, contourné ou simulé, il suffit d'ouvrir les outils de développement du navigateur ou de rejouer la requête à la main. Seul le serveur a le dernier mot.
Dernier point, la sécurité n'est pas une case à cocher en fin de projet. Les dépendances vieillissent, des failles sont découvertes dans les bibliothèques qu'on utilise, et une API parfaitement sécurisée en janvier ne l'est plus en décembre si personne ne l'a mise à jour. La surveillance des journaux, la rotation régulière des clés et la mise à jour des dépendances font partie du travail quotidien, comme nous l'avons vu dans le chapitre sur la cybersécurité.