Chapitre 923 minIntermédiaire
Authentifier une API : clé d'API, Bearer, JWT et OAuth 2.0
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- choisir un mécanisme d'authentification selon le client ;
- lire un JWT et dire ce que sa signature garantit ;
- vérifier un jeton côté API sans laisser de faille ;
- expliquer les flux OAuth 2.0 utiles à une API.
5 exercices corrigés2 schémas
Authentifier une API, c'est vérifier à chaque requête qui l'envoie, puisque HTTP ne garde aucun état entre deux appels. Le client joint une preuve à sa requête, le plus souvent dans l'en-tête Authorization : une clé d'API pour un programme partenaire, un jeton (souvent un JWT) pour un utilisateur, un jeton d'accès OAuth 2.0 pour une application tierce. Ce chapitre présente chaque mécanisme, son fonctionnement, ses pièges et les cas où le choisir.
Authentifier, puis autoriser
Deux questions différentes, posées dans cet ordre à chaque requête :
- l'authentification : qui es-tu ? L'API vérifie la preuve jointe à la requête. Échec :
401 Unauthorized; - l'autorisation : as-tu le droit de faire cette action sur cette ressource ? L'API compare l'identité aux règles : le rôle, le propriétaire de la ressource, l'état de la ressource. Échec :
403 Forbidden(ou404pour ne pas révéler l'existence).
Ce chapitre traite de la première question ; la seconde, où se cachent les failles les plus graves des API, est au chapitre 10. Les notions d'identification, d'authentification et d'autorisation sont détaillées dans le chapitre Identification, authentification, autorisation du cours Authentification.
Les mécanismes en un coup d'œil
| Mécanisme | Ce qui voyage dans chaque requête | Qui est identifié | Usage typique |
|---|---|---|---|
| Aucun | Rien | Personne | API publique en lecture (données ouvertes), avec limite de débit |
| HTTP Basic | Authorization: Basic + identifiant et mot de passe encodés | Un compte | Outils internes, scripts ; à éviter pour des utilisateurs |
| Clé d'API | X-API-Key: … ou Authorization: … | Une application ou une organisation | Partenaire qui appelle de serveur à serveur |
| Cookie de session | Cookie: session=… (ajouté par le navigateur) | Un utilisateur | Front servi par le même domaine que l'API |
| Jeton porteur (Bearer), opaque ou JWT | Authorization: Bearer … | Un utilisateur, pour une durée courte | Front web, application mobile |
| Jeton d'accès OAuth 2.0 | Authorization: Bearer … | Une application, au nom d'un utilisateur ou pour elle-même, avec des droits limités | Application tierce, SSO, échanges entre services |
| Certificat client (TLS mutuel) | Le certificat, pendant la poignée de main TLS | Une machine | Échanges entre services d'une même organisation, banques |
Une règle s'applique à tous : tout passe en HTTPS. Une clé ou un jeton qui circule en clair est volé par le premier qui écoute le réseau.
HTTP Basic
Le client envoie l'identifiant et le mot de passe, séparés par deux-points et encodés en base64, à chaque requête :
GET /v1/fiches HTTP/1.1
Host: api.gsb.example
Authorization: Basic YXBwbGktcGFpZTpTM2NyM3QtUGFydDNuYWlyZQ==
YXBwbGktcGFpZTpTM2NyM3QtUGFydDNuYWlyZQ== se décode en appli-paie:S3cr3t-Part3naire. Le base64 est un encodage, pas un chiffrement : n'importe qui le décode en une ligne (atob(…) dans la console du navigateur). Basic est simple et universel, mais le mot de passe voyage à chaque requête, ne peut pas avoir de durée de vie courte et ne se combine pas avec une authentification multifacteur. On le réserve à des outils internes ou à des scripts, toujours en HTTPS.
La clé d'API
Une clé d'API est une longue chaîne aléatoire que l'API remet à une application cliente, en général celle d'un partenaire. L'application la joint à chaque requête :
GET /v1/fiches?etat=VA HTTP/1.1
Host: api.gsb.example
X-API-Key: gsb_live_9c1f2e7a4b6d8f0a3c5e7b9d1f3a5c7e
Une clé d'API identifie une application, pas une personne : elle dit « c'est le logiciel de paie du cabinet comptable », pas « c'est Lucas ». Elle a une longue durée de vie, ce qui impose des précautions :
- une clé par client, avec des droits limités à ce dont il a besoin (lecture seule des fiches validées, par exemple) ;
- stockée hachée côté serveur (avec SHA-256, la clé étant assez longue et aléatoire pour que le sel soit inutile), comme un mot de passe : une fuite de la base ne livre pas les clés utilisables. Un préfixe lisible (
gsb_live_) aide à repérer une clé publiée par erreur dans un dépôt Git ; - révocable et renouvelable sans interrompre le service : l'API accepte deux clés le temps de la transition ;
- jamais dans une URL (
?api_key=…) : les URL sont enregistrées dans les journaux des serveurs et des proxys, dans l'historique et dans l'en-têteReferer; - jamais dans un front web ni une application mobile : ce sont des clients publics, qui ne peuvent rien garder secret (chapitre 8).
Le jeton porteur : se connecter une fois
Pour un utilisateur humain, on ne renvoie pas son mot de passe à chaque requête. Il se connecte une fois ; l'API lui remet un jeton de courte durée, qu'il présente ensuite à chaque appel.
La connexion :
POST /v1/auth/connexion HTTP/1.1
Host: api.gsb.example
Content-Type: application/json
{"login":"lmartin","motDePasse":"…"}
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3Iiwicm9sZSI6InZpc2l0ZXVyIiwiaWF0IjoxNzkzNjA2NDAwLCJleHAiOjE3OTM2MDczMDB9.a8ZshHZGh7E-8WGbfhx71TRtZuYh4itGWIpPi0G4Psg","tokenType":"Bearer","expiresIn":900,"refreshToken":"Xq7Lk2vR9pT4wZ8nB3mF6yH1cJ5sD0aE"}
Puis chaque requête porte le jeton dans l'en-tête Authorization, avec le mot Bearer (« porteur ») :
GET /v1/fiches HTTP/1.1
Host: api.gsb.example
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3Iiwicm9sZSI6InZpc2l0ZXVyIiwiaWF0IjoxNzkzNjA2NDAwLCJleHAiOjE3OTM2MDczMDB9.a8ZshHZGh7E-8WGbfhx71TRtZuYh4itGWIpPi0G4Psg
« Porteur » veut dire que quiconque possède le jeton est reconnu comme Lucas, comme un billet de train non nominatif. D'où les règles : HTTPS, durée de vie courte, jamais dans une URL ni dans un journal.
Sans jeton valide, l'API répond 401 avec un en-tête qui explique le problème (RFC 6750) :
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token", error_description="Le jeton a expiré"
Le jeton peut être opaque (une chaîne aléatoire que l'API retrouve dans sa base ou dans un cache) ou autoporteur, c'est-à-dire qu'il contient lui-même les informations sur l'utilisateur, signées : c'est le cas du JWT.
Le JWT : structure
Un JWT (JSON Web Token, RFC 7519, prononcé « jot ») se compose de trois parties encodées en base64url et séparées par des points : l'en-tête, la charge utile et la signature.
Décodé, le jeton de Lucas donne :
{ "alg": "HS256", "typ": "JWT" }
{ "sub": "7", "role": "visiteur", "iat": 1793606400, "exp": 1793607300 }
Les revendications (claims) normalisées les plus courantes :
| Revendication | Sens | Dans l'exemple |
|---|---|---|
sub (subject) | L'identifiant de l'utilisateur | "7" : le visiteur n° 7 |
iat (issued at) | Date d'émission, en secondes depuis le 1er janvier 1970 UTC | 1793606400, le 2 novembre 2026 à 9 h 00 (heure de Paris) |
exp (expiration) | Date d'expiration ; au-delà, le jeton doit être refusé | 1793607300, quinze minutes plus tard |
iss (issuer) | Qui a émis le jeton | "https://api.gsb.example" |
aud (audience) | Pour quelle API il est destiné | "api-applifrais" |
nbf, jti | Date avant laquelle il n'est pas valable ; identifiant unique du jeton | Pour les usages avancés |
role n'est pas normalisé : c'est une revendication propre à l'application, comme on peut en ajouter d'autres.
La signature est calculée sur les deux premières parties avec une clé que seul le serveur connaît. Deux familles d'algorithmes :
- symétriques (
HS256, HMAC avec SHA-256) : la même clé secrète signe et vérifie. Simple, adapté quand c'est la même API qui émet et vérifie ; - asymétriques (
RS256,ES256) : une clé privée signe, une clé publique vérifie. Adapté quand un fournisseur d'identité émet les jetons et que plusieurs API les vérifient, sans jamais détenir la clé privée. Les clés publiques sont souvent publiées à une adresse/.well-known/jwks.json.
Le fonctionnement du HMAC et des signatures est expliqué dans le chapitre HMAC et signature numérique du cours de cryptographie.
Vérifier un JWT côté API
Toute la sécurité repose sur la vérification. Le middleware de l'API, ici en Node.js avec la bibliothèque jsonwebtoken :
const jwt = require('jsonwebtoken');
function authentifier(req, res, next) {
const entete = req.headers.authorization ?? '';
const [schema, jeton] = entete.split(' ');
if (schema !== 'Bearer' || !jeton) {
res.set('WWW-Authenticate', 'Bearer realm="api"');
return res.status(401).type('application/problem+json').json({
type: 'https://api.gsb.example/problemes/non-authentifie',
title: 'Authentification requise',
status: 401,
});
}
try {
req.utilisateur = jwt.verify(jeton, process.env.JWT_SECRET, {
algorithms: ['HS256'], // n'accepter que l'algorithme prévu
issuer: 'https://api.gsb.example',
audience: 'api-applifrais',
}); // verify contrôle aussi exp et nbf
next();
} catch {
res.set('WWW-Authenticate', 'Bearer realm="api", error="invalid_token"');
return res.status(401).type('application/problem+json').json({
type: 'https://api.gsb.example/problemes/jeton-invalide',
title: 'Jeton invalide ou expiré',
status: 401,
});
}
}
Les contrôles à ne pas oublier, et les failles qu'ils évitent :
| Contrôle | Faille si on l'oublie |
|---|---|
Vérifier la signature (verify, jamais decode) | jwt.decode() lit la charge utile sans rien vérifier : un jeton fabriqué à la main est accepté |
| Imposer la liste des algorithmes | Un jeton avec "alg":"none" (pas de signature) ou un algorithme détourné est accepté par une bibliothèque mal configurée |
Vérifier exp | Un jeton volé reste utilisable pour toujours |
Vérifier iss et aud | Un jeton émis pour une autre application du même fournisseur est accepté |
| Une clé secrète longue et aléatoire, hors du code | Une clé courte (secret, gsb2026) se retrouve par force brute à partir d'un seul jeton ; une clé dans le dépôt Git est publique |
Les recommandations de l'IETF sur l'usage des JWT sont rassemblées dans la RFC 8725.
Jeton d'accès court, jeton de rafraîchissement
Un JWT ne se révoque pas facilement : il reste valable jusqu'à son exp, même si l'utilisateur se déconnecte ou est désactivé, puisque l'API ne consulte aucune base pour le vérifier. On limite le risque en combinant deux jetons :
- un jeton d'accès (access token) court (5 à 15 minutes), présenté à chaque requête ;
- un jeton de rafraîchissement (refresh token) long (quelques jours), présenté seulement à la route de rafraîchissement, et enregistré côté serveur, donc révocable.
Quand le jeton d'accès expire, le client reçoit 401, appelle POST /v1/auth/rafraichissement avec son jeton de rafraîchissement et obtient un nouveau jeton d'accès, sans redemander le mot de passe. Bonne pratique : à chaque rafraîchissement, l'API remet aussi un nouveau jeton de rafraîchissement et invalide l'ancien (la rotation). Si un ancien jeton est présenté une seconde fois, c'est qu'il a été volé : l'API révoque toute la session.
Où le front web garde-t-il ses jetons ? Dans localStorage, ils sont lisibles par tout script de la page, donc volés par la moindre faille XSS ; dans un cookie HttpOnly, Secure, SameSite, ils sont invisibles au JavaScript mais envoyés automatiquement, ce qui impose de se protéger du CSRF. Le compromis est détaillé dans le chapitre Sessions, cookies et JWT du cours Authentification.
| Jeton opaque | JWT | |
|---|---|---|
| Contenu | Une chaîne aléatoire sans signification | Des informations signées, lisibles par tous |
| Vérification | Consultation d'une base ou d'un cache à chaque requête | Calcul de la signature, sans base |
| Révocation | Immédiate (on supprime la ligne) | Difficile avant exp |
| Taille | Courte | Plusieurs centaines d'octets |
| Adapté à | Une API qui émet et vérifie elle-même ses jetons | Plusieurs API qui vérifient les jetons d'un même émetteur |
OAuth 2.0 : déléguer un accès
OAuth 2.0 (RFC 6749) est un protocole d'autorisation déléguée : il permet à une application d'obtenir un jeton d'accès limité pour appeler une API, sans jamais connaître le mot de passe de l'utilisateur. Quatre rôles :
- le propriétaire de la ressource : l'utilisateur ;
- le client : l'application qui veut accéder à l'API ;
- le serveur d'autorisation : il authentifie l'utilisateur, recueille son consentement et délivre les jetons (Keycloak, Microsoft Entra ID, Google…) ;
- le serveur de ressources : l'API, qui vérifie le jeton d'accès à chaque requête.
Le jeton d'accès porte des portées (scopes) qui limitent ce qu'il permet : fiches:lecture, fiches:ecriture. L'API vérifie la portée en plus du rôle.
Deux flux suffisent à connaître aujourd'hui. Les recommandations de sécurité de l'IETF publiées en janvier 2025 (RFC 9700) écartent le flux implicite et le flux qui transmet le mot de passe au client.
Le flux du code d'autorisation avec PKCE, pour une application utilisée par une personne (front web, application mobile). Le client redirige l'utilisateur vers le serveur d'autorisation, qui l'authentifie et renvoie un code à usage unique ; le client échange ce code contre les jetons, en prouvant avec PKCE qu'il est bien celui qui a lancé la demande. Le détail pas à pas, avec ses paramètres, est dans le chapitre OAuth 2.0, OpenID Connect, SAML du cours Authentification.
Le flux des identifiants client (client credentials), pour un échange entre machines, sans utilisateur. Le logiciel de paie du cabinet comptable s'authentifie avec son identifiant et son secret, et obtient un jeton pour lui-même :
POST /oauth/token HTTP/1.1
Host: auth.gsb.example
Content-Type: application/x-www-form-urlencoded
Authorization: Basic YXBwbGktcGFpZTpTM2NyM3QtUGFydDNuYWlyZQ==
grant_type=client_credentials&scope=fiches%3Alecture
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{"access_token":"eyJhbGciOiJSUzI1NiIs…","token_type":"Bearer","expires_in":3600,"scope":"fiches:lecture"}
Le logiciel de paie appelle ensuite l'API avec Authorization: Bearer …, et redemande un jeton quand il expire. Par rapport à une clé d'API permanente, le jeton est court, limité à une portée, et la gestion des clients est centralisée dans le serveur d'autorisation.
Quel mécanisme choisir ?
| Situation | Choix | Pourquoi |
|---|---|---|
| Le front React d'AppliFrais, servi sur un autre domaine que l'API | Jeton d'accès court + jeton de rafraîchissement | Un utilisateur, une durée courte, une révocation possible |
| Le front servi par le même domaine que l'API | Cookie de session HttpOnly, Secure, SameSite | Le plus simple et le plus sûr face au XSS ; protection CSRF à prévoir |
| L'application mobile des visiteurs | Code d'autorisation + PKCE, ou connexion directe à l'API ; jeton de rafraîchissement dans le stockage sécurisé du téléphone | Un client public, qui ne peut garder aucun secret |
| Le logiciel du cabinet comptable, qui lit les fiches validées chaque nuit | Clé d'API limitée en lecture, ou identifiants client OAuth 2.0 | Une application, sans utilisateur, de serveur à serveur |
| Une API de données publiques en lecture | Aucune authentification, mais une limite de débit par adresse IP | Rien à protéger en confidentialité ; la disponibilité, si |
| Deux services internes dans le même centre de données | TLS mutuel, ou identifiants client OAuth 2.0 | Authentifier des machines |
Le sujet E7 SLAM 2025 de Nouvelle-Calédonie (cas Kirassur) combinait les deux premiers mondes : la société partenaire s'authentifiait avec sa clé d'API pour obtenir un jeton temporaire au nom d'un assuré, et l'assuré n'utilisait que ce jeton, jamais la clé. Le raisonnement est développé dans le chapitre API, jetons et authentification du cours E7 SLAM.
Exercices
Exercice 1 sur 5 Décoder un JWT Réussi À revoir
Un comptable te transmet ce jeton pour comprendre un refus d'accès :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsInJvbGUiOiJjb21wdGFibGUiLCJleHAiOjE3OTM2MTA5MDB9.<signature>
- Décode la charge utile (dans la console du navigateur,
atob()lit le base64 ; ici, aucun caractère-ni_n'est à convertir). - À quelle date et heure (heure de Paris) le jeton expire-t-il ?
expvaut un nombre de secondes depuis le 1er janvier 1970 UTC ;new Date(exp * 1000)fait la conversion. - Le comptable a modifié
roleenadmindans un outil en ligne et renvoyé le jeton. Que doit répondre l'API, et pourquoi ? - Pourquoi ne faut-il pas coller un vrai jeton dans un site de décodage en ligne ?
Voir la réponseMasquer la réponse de l'exercice 1
{"sub":"42","role":"comptable","exp":1793610900}: l'utilisateur 42, de rôle comptable.new Date(1793610900 * 1000)donne le 2 novembre 2026 à 9 h 15 UTC, soit 10 h 15 à Paris (UTC + 1 en novembre).401: la charge utile modifiée ne correspond plus à la signature, calculée avec la clé secrète du serveur que le comptable ne connaît pas. Le jeton est invalide, à condition que l'API vérifie bien la signature (verify, pasdecode).- Un jeton valide est un porteur : celui qui le possède est authentifié jusqu'à son expiration. Le coller dans un site tiers, c'est le confier à un inconnu. On décode localement (console, script), ou on n'utilise que des jetons de test.
Tu avais trouvé ?
Exercice 2 sur 5 Choisir le mécanisme Réussi À revoir
Pour chaque cas, choisis le mécanisme d'authentification et justifie en une phrase.
- Un script PowerShell interne interroge chaque matin l'API de supervision pour récupérer l'état des serveurs.
- L'application mobile d'une enseigne permet aux clients de consulter leurs points de fidélité.
- Une agence de voyage partenaire réserve des hôtels pour les visiteurs de GSB via l'API de réservation du laboratoire.
- L'API publique des horaires d'ouverture des pharmacies de garde d'un département.
Voir la réponseMasquer la réponse de l'exercice 2
- Clé d'API (ou identifiants client OAuth 2.0) dédiée au script, limitée en lecture, stockée dans un coffre ou une variable protégée du serveur : pas d'utilisateur, un échange de machine à machine.
- Code d'autorisation avec PKCE (ou connexion directe) puis jetons d'accès courts et jeton de rafraîchissement dans le stockage sécurisé du téléphone : un utilisateur, sur un client public qui ne peut garder aucun secret.
- Clé d'API par partenaire, ou mieux identifiants client OAuth 2.0 avec une portée limitée à la réservation : une organisation identifiée, droits minimaux, révocable si le contrat s'arrête.
- Aucune authentification, mais une limite de débit par adresse IP : les données sont publiques, il faut seulement protéger la disponibilité.
Tu avais trouvé ?
Exercice 3 sur 5 Auditer un middleware Réussi À revoir
Repère les failles de ce middleware.
function authentifier(req, res, next) {
const jeton = req.query.token;
const donnees = jwt.decode(jeton);
if (!donnees) return res.status(403).send('Interdit');
req.utilisateur = donnees;
next();
}
Voir la réponseMasquer la réponse de l'exercice 3
- Le jeton est lu dans l'URL (
?token=) : il finit dans les journaux du serveur et du proxy, l'historique du navigateur et l'en-têteReferer. Il doit venir de l'en-têteAuthorization: Bearer. jwt.decodene vérifie rien : ni la signature, ni l'expiration. N'importe qui fabrique un jeton{"sub":"1","role":"admin"}et passe. Il fautjwt.verifyavec la clé secrète et la liste des algorithmes permis.- Aucun contrôle de
exp,iss,aud(queverifyassure avec les bonnes options). - Mauvais code : un jeton absent ou invalide appelle
401avecWWW-Authenticate, pas403. - La réponse d'erreur n'est pas au format de l'API (
application/problem+json).
Tu avais trouvé ?
Exercice 4 sur 5 401 ou 403 ? Réussi À revoir
Lucas, visiteur, appelle l'API. Donne le code de chaque réponse.
- Son jeton a expiré il y a deux minutes.
- Son jeton est valide ; il demande
GET /v1/fiches?etat=CL&visiteur=9(les fiches d'un autre visiteur), action réservée aux comptables. - Il envoie
Authorization: Bearersans rien derrière. - Son jeton contient une charge utile correcte (
sub,role,expdans le futur), mais son en-tête indique"alg":"none"et il n'a pas de signature. - Son jeton est valide ; il supprime un frais hors forfait de sa propre fiche, qui est clôturée.
Voir la réponseMasquer la réponse de l'exercice 4
401, avecWWW-Authenticate: Bearer error="invalid_token": le client utilise son jeton de rafraîchissement.403: il est authentifié, mais son rôle ne lui permet pas de consulter les fiches des autres (ou une liste réduite à ses propres fiches, si l'API ignore le filtrevisiteurpour un visiteur).401: aucun jeton.401: un jeton sans signature n'est pas valide ; l'API n'accepte que l'algorithme prévu.409: ni l'authentification ni le droit ne sont en cause, c'est l'état de la fiche qui interdit l'opération.
Tu avais trouvé ?
Exercice 5 sur 5 Clé d'API ou jeton ? Réussi À revoir
Un partenaire propose : « Pour que nos clients consultent leur dossier depuis notre site, il suffit de leur transmettre votre clé d'API, de toute façon tout passe en HTTPS. » Explique pourquoi c'est une mauvaise idée et propose une alternative.
Voir la réponseMasquer la réponse de l'exercice 5
HTTPS protège la clé pendant le transport, pas ce que le destinataire en fait. La clé est permanente et donne accès à tous les dossiers que le partenaire peut consulter : remise à un client, elle peut être copiée, publiée, ou utilisée pour lire les dossiers des autres clients, et on ne peut la révoquer sans couper le partenaire entier. L'alternative : le serveur du partenaire, seul détenteur de la clé, demande à l'API un jeton temporaire (quelques minutes), limité au dossier de ce client, et c'est ce jeton qui est transmis au client. On applique ainsi le moindre privilège : un utilisateur, un périmètre, une durée courte.
Tu avais trouvé ?