Qu’est-ce qu’une clé API ?
Une clé API est une chaîne de caractères secrète et durable qui identifie une application plutôt qu’une personne. Le client l’envoie avec chaque requête, le serveur la reconnaît, et l’accès est accordé selon les permissions associées à cette clé. C’est tout simplement ainsi que cela fonctionne.
C’est un identifiant volontairement simple. Il n’y a pas de connexion, pas d’écran de consentement et pas d’échange de jetons. Un développeur s’inscrit, crée une clé, la colle dans un fichier de configuration, et son code commence à fonctionner. C’est cette absence de friction qui explique pourquoi presque toutes les plateformes pour développeurs — paiements, cartes, e-mails, infrastructure — distribuent des clés.
Cependant, simple ne signifie pas négligent. Une clé est un identifiant de type “bearer” (au porteur) : quiconque la détient peut l’utiliser, exactement comme avec de l’argent liquide. Il n’y a pas de second facteur ni de signature. Cela signifie que la sécurité de l’ensemble du système repose sur la qualité de la génération des clés, le soin apporté à leur stockage, la précision de leur périmètre d’action et la rapidité avec laquelle vous pouvez révoquer une clé en cas de fuite. Ce guide a pour but de vous aider à maîtriser ces quatre aspects.
Quand utiliser une clé
Les clés ne sont pas la solution à tous les problèmes d’authentification, et les utiliser à tort crée des risques réels.
Optez pour une API key lorsqu’un serveur communique avec un autre serveur, lorsque l’appelant est une application à laquelle vous pouvez confier un secret à longue durée de vie, ou lorsque vous proposez une API publique aux développeurs. Les pipelines CI, les intégrations backend, les agents de monitoring et les services tiers sont des cas d’usage naturels. La clé est stockée dans un gestionnaire de secrets ou une variable d’environnement, et elle ne transite jamais par un navigateur.
Optez pour OAuth lorsque vous devez agir au nom d’un utilisateur. Si votre intégration doit lire le calendrier de quelqu’un ou envoyer des e-mails en son nom, vous avez besoin d’un flux de consentement et d’un token représentant cette délégation. Une clé ne peut pas exprimer : « ceci sont les données d’Alice et Alice a donné son accord ».
Optez pour un JWT lorsque vous avez besoin d’un token vérifiable à courte durée de vie, transportant des claims et pouvant être validé sans base de données partagée. L’authentification de service à service dans un mesh, ou une URL de téléchargement signée, en sont de bons exemples.
Le cas dangereux est celui du client public. Une clé intégrée dans une application mobile, un binaire desktop ou un bundle navigateur n’est pas secrète : n’importe qui peut l’extraire. Si votre produit nécessite que ces clients appellent votre API, placez un proxy backend devant, ou émettez des tokens à courte durée de vie depuis votre propre serveur après avoir authentifié l’utilisateur. N’expédiez jamais de clé à longue durée de vie à l’intérieur du code client.
L’anatomie d’une bonne clé
Une bonne clé doit être imprévisible et auto-descriptive. Elle se compose de deux parties : un préfixe court et lisible, et un secret long et aléatoire.
Le préfixe remplit deux fonctions. Il permet d’identifier l’environnement et le type de la clé en un coup d’œil, et il fournit au serveur un identifiant indexé pour la recherche sans avoir à stocker le secret. Un format fixe et reconnaissable rend également les fuites accidentelles détectables par les scanners de secrets, qui peuvent repérer sk_live_ dans un commit et le bloquer.
Le secret doit provenir d’une source aléatoire cryptographiquement sécurisée, et jamais de Math.random, d’un horodatage ou d’un UUID qui trahirait une structure. 128 bits d’entropie constituent le minimum ; 256 bits sont un choix par défaut confortable et ne coûtent rien. L’encodage Base64url permet de coller la clé en toute sécurité dans des URLs et des headers sans avoir besoin d’échappement.
const secret = crypto.randomBytes(32).toString("base64url");
const key = `sk_live_${secret}`;
Résistez à la tentation d’encoder des informations dans la clé, comme l’ID de l’utilisateur ou une date de création. Tout élément lisible dans la clé est une information dont un attaquant peut tirer profit, et tout élément dérivé de données prévisibles affaiblit le caractère aléatoire. Le préfixe est la seule partie lisible dont vous avez besoin, et il ne doit révéler aucune donnée sensible.
Affichez-la une seule fois, puis oubliez-la
La clé complète ne doit exister qu’à un seul endroit après sa création : la réponse renvoyée à l’utilisateur. À partir de ce moment, le serveur ne stocke plus qu’un hash et un préfixe, ce qui signifie qu’il peut vérifier une clé présentée, mais ne pourra jamais la reproduire.
C’est la même propriété que pour le stockage des mots de passe, et cela change radicalement les conséquences d’une faille de sécurité. Si un attaquant récupère le contenu de la table api_keys, il n’obtiendra que des hashs qui ne peuvent pas être envoyés à votre API. Sans hachage, une simple fuite de base de données ou l’exposition d’une sauvegarde livrerait instantanément les clés de tous vos clients.
L’expérience utilisateur découle de cette contrainte technique. Le tableau de bord affiche la clé une seule fois, avec un avertissement clair demandant de la copier immédiatement, puis n’affiche plus que le préfixe et des métadonnées telles que les scopes et la date de dernière utilisation. Si un utilisateur perd sa clé, il doit la renouveler (rotate) ; il ne peut pas la récupérer. Précisez-le explicitement dans votre documentation pour que personne ne s’attende à la retrouver plus tard.
res.status(201).json({
id: row.id,
name: row.name,
prefix: row.prefix,
key, // shown once, never retrievable again
warning: "Store this key now. You will not be able to see it again.",
});
Stockez un hash, jamais la clé
Le hachage est le contrôle le plus important d’un système de clés API. C’est également celui que les équipes oublient le plus souvent, généralement parce qu’elles souhaitent pouvoir afficher la clé à nouveau plus tard. Ne le faites pas.
Utilisez un hash cryptographique rapide tel que SHA-256. Contrairement aux mots de passe, les clés possèdent une entropie complète ; il n’y a donc pas de dictionnaire d’attaque et aucun besoin pour un KDF lent. Le hash rapide permet également de maintenir un coût de vérification faible sur le chemin critique.
function hashKey(key: string): string {
return crypto.createHash("sha256").update(key).digest("hex");
}
La comparaison doit être effectuée en temps constant. Un === naïf sur des chaînes de caractères s’arrête dès le premier octet différent, ce qui révèle quelle partie d’une clé devinée était correcte. C’est généralement une préoccupation théorique sur un réseau, mais c’est trivial à éviter et c’est une bonne pratique d’hygiène. Comparez des buffers de longueur égale avec crypto.timingSafeEqual.
const a = Buffer.from(hashKey(presented));
const b = Buffer.from(record.keyHash);
const ok = a.length === b.length && crypto.timingSafeEqual(a, b);
Si vous souhaitez masquer complètement le hash dans la base de données, un hash clé avec un “pepper” côté serveur ajoute un second secret que l’attaquant doit également obtenir. C’est optionnel pour les systèmes de haute sécurité, mais le hachage, lui, n’est pas optionnel.
Rechercher des clés sans scan complet
Le hachage pose un problème pratique : vous ne pouvez pas interroger la base de données par hash à moins de hacher la clé entrante au préalable, et vous ne pouvez pas hacher la clé entrante tant que vous ne savez pas avec quelle ligne la comparer. Hacher chaque ligne à chaque requête n’est pas envisageable.
La solution est l’index de préfixe. Stockez les douze premiers caractères de la clé dans une colonne prefix en texte clair avec un index unique. Lorsqu’une requête arrive, lisez le préfixe, trouvez la seule ligne correspondante, puis hachez la clé complète présentée et comparez-la au key_hash de cette ligne. Une seule recherche indexée, un seul hachage, une seule comparaison en temps constant.
const prefix = key.slice(0, 12);
const record = await db.apiKey.findByPrefix(prefix);
if (!record || record.revokedAt) return res.status(401).end();
const presented = hashKey(key);
if (!timingSafeEqual(presented, record.keyHash)) {
return res.status(401).end();
}
Quelques détails permettent de garantir la robustesse du système. Le préfixe doit être assez long pour que les collisions soient rares, mais assez court pour rester un label utile ; douze caractères est un choix courant. Si une collision survient, l’index unique échoue lors de la création et vous régénérez la clé. Renvoyez la même erreur pour un préfixe inconnu et un secret erroné, afin que la réponse ne révèle pas si un préfixe existe. Enfin, mettez à jour last_used_at de manière asynchrone ou via une écriture groupée (batch), car une mise à jour synchrone à chaque requête transforme une lecture en écriture et double la charge de votre base de données.
Associer les clés à des permissions et des limites
Une clé capable de tout faire est une clé dont la fuite serait catastrophique. Limitez chaque clé au plus petit ensemble de permissions nécessaires à l’accomplissement de sa tâche.
Les scopes ne sont que des chaînes de permissions, les mêmes atomes utilisés par le RBAC : projects:read, deploy:write, billing:manage. Stockez-les sur la clé, attachez-les à la requête après vérification, et appliquez-les exactement comme vous le feriez pour les permissions d’un utilisateur.
router.post(
"/deployments",
apiKeyAuth(),
requireScope("deploy:write"),
createDeployment
);
C’est ici que le principe du moindre privilège devient concret. Une intégration de monitoring n’a besoin que de metrics:read. Un pipeline CI a besoin de deploy:write mais jamais de billing:manage. Un partenaire en lecture seule reçoit des scopes de lecture et rien d’autre. Lorsqu’une clé fuite, le rayon d’impact est limité à son scope, c’est pourquoi la configuration par défaut devrait être une liste restreinte qu’un humain étend délibérément.
Les scopes rendent également les rate limits naturels et équitables. Puisque chaque clé est un appelant distinct, vous pouvez associer un quota par clé et l’appliquer dans votre couche de limitation de débit, afin qu’un script incontrôlé ne puisse pas consommer la capacité destinée à tous. Les plans tarifaires sont souvent mappés directement sur le quota : les clés gratuites ont un plafond bas, les clés payantes un plafond plus élevé.
Préfixes d’environnement
Les préfixes ne sont pas là pour la décoration. Ils encodent l’environnement et le type d’une clé, ce qui permet d’éviter l’un des modes de panne les plus courants et embarrassants : une clé de test pointant vers la production, ou une clé de production utilisée dans une suite de tests qui vient ensuite modifier des données réelles.
Une convention courante consiste à utiliser sk_live_ pour les clés secrètes en production et sk_test_ pour le sandbox. Les clés publiables ou publiques peuvent utiliser pk_live_. Le nommage vous revient, mais restez cohérent et documentez-le, car vos utilisateurs s’appuieront dessus pour savoir d’un coup d’œil ce qu’une clé peut modifier.
sk_live_... secret key, production, full access within its scopes
sk_test_... secret key, sandbox, no real data
pk_live_... publishable key, safe to embed in a browser
Le préfixe permet également à votre serveur de router les requêtes vers le bon environnement avant même toute recherche. Une clé sk_test_ présentée à l’API de production peut être rejetée immédiatement avec un message clair, plutôt que de provoquer une erreur d’authentification opaque. Et parce que le préfixe est fixe et distinctif, les scanners de secrets et les outils de revue de code peuvent être configurés pour le signaler.
Envoyer une clé : header ou query
L’endroit où la clé transite est aussi important que la manière dont elle est stockée. Envoyez-la dans un header.
GET /v1/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_8f2a9c...
Les headers ne sont pas inclus dans les logs d’accès par défaut, n’apparaissent pas dans l’historique du navigateur et ne sont pas transmis dans le header Referer lorsqu’une page renvoie vers un autre site. Ils sont également faciles à masquer dans les logs et les proxys. Le header Authorization avec un schéma Bearer est conventionnel et supporté par la plupart des clients HTTP et des outils ; un header X-API-Key dédié convient tout autant.
Les query strings ne sont pas l’endroit approprié. Elles sont capturées par les logs d’accès, mises en cache par des intermédiaires, stockées dans l’historique du navigateur et les outils d’analytics, et fuitent via le Referer. Une clé dans une URL est une clé présente dans une douzaine d’endroits que vous ne contrôlez pas.
GET /v1/projects?api_key=sk_live_8f2a9c... HTTP/1.1
Si un client ne peut réellement pas définir de headers — comme dans certains scénarios de webhooks legacy ou d’intégration (embed) — utilisez plutôt un token à courte durée de vie et à portée restreinte dans la query string, et faites-le expirer rapidement. N’acceptez jamais une clé à longue durée de vie dans une URL, et si vos logs risquent d’en contenir, nettoyez-les.
Rotation et révocation
Toute clé devra tôt ou tard être changée. Un employé quitte l’entreprise, un ordinateur portable est perdu, une clé apparaît dans un dépôt public, ou une politique de routine impose simplement une rotation périodique. Prévoyez cela dès le départ, car intégrer la rotation a posteriori est fastidieux.
La rotation consiste à émettre un remplacement sans interrompre le service pour le client. Le schéma est le suivant : créez une nouvelle clé avec les mêmes scopes, retournez-la, autorisez les deux clés à fonctionner pendant une courte fenêtre de chevauchement, puis révoquez l’ancienne. C’est ce chevauchement qui permet une rotation sans interruption de service (zero-downtime), et le fait de publier sa durée permet aux intégrateurs de s’organiser.
// 1. Issue the replacement and return it to the owner.
// 2. Keep both keys valid for the overlap window (e.g. 24 hours).
// 3. Revoke the old key, or let it expire automatically.
await db.apiKey.revoke(oldKeyId);
La révocation est immédiate et permanente. Définissez revoked_at sur la ligne, et faites en sorte que le middleware de vérification rejette toute clé ayant une valeur non nulle. Ne supprimez pas la ligne : la conserver permet de maintenir la piste d’audit et d’empêcher la réutilisation du même préfixe. La révocation doit prendre effet dès la requête suivante, ce qui est facile lorsque la clé est vérifiée via la base de données, mais impossible lorsqu’il s’agit d’un token autonome.
Prévoyez la possibilité de révoquer une seule clé, toutes les clés d’un utilisateur, ou toutes les clés d’un tenant. Les deux dernières options correspondent au bouton « je pense que nous avons été piratés », et elles doivent être accessibles en un clic. Alertez le propriétaire chaque fois qu’une clé est créée ou révoquée, afin qu’un attaquant ayant accédé au tableau de bord ne puisse pas générer discrètement un nouvel identifiant.
Fuites : git, logs et code client
La plupart des compromissions de clés API ne sont pas des attaques sophistiquées. Il s’agit simplement d’une clé qui se trouve là où elle ne devrait pas être.
Le contrôle de source est le cas classique. Une clé collée dans un fichier de configuration, un fixture de test ou un .env qui est commité reste dans l’historique git pour toujours, même si un commit ultérieur la supprime. Scannez vos commits et vos pull requests pour détecter des motifs de clés, conservez vos secrets dans un gestionnaire ou des variables CI, et effectuez une rotation immédiate si l’une d’elles est commitée. Partez du principe qu’une clé présente dans un dépôt public est déjà compromise.
Les logs sont les fuites les plus discrètes. Un logger de requêtes qui affiche les URLs complètes, les headers ou les corps de requêtes peut capturer des millions de clés. Configurez votre logger pour masquer les headers Authorization et X-API-Key, ne loggez jamais les corps de requêtes sur les endpoints d’authentification, et auditez vos logs pour repérer des chaînes de caractères ressemblant à des clés.
Le code côté client est l’erreur fatale. Une clé dans un bundle navigateur, une application mobile ou un binaire desktop peut être extraite en quelques minutes. Aucune technique d’obfuscation ne peut corriger cela. Placez un serveur en amont ou émettez des tokens à courte durée de vie.
Autres points à vérifier : les messages d’erreur et les stack traces, les outils d’analytics tiers, les captures d’écran dans les rapports de bugs, et les ordinateurs de développeurs synchronisés avec une sauvegarde partagée. Considérez chacun de ces points comme une fuite potentielle et donnez aux utilisateurs les outils nécessaires pour réagir lorsque cela arrive.
Surveillance de l’utilisation et des anomalies
Une clé qui n’est jamais surveillée ne peut pas être protégée. Enregistrez suffisamment d’informations sur chaque utilisation pour détecter les usages abusifs et répondre aux questions après un incident.
Au minimum, stockez last_used_at et la source de la requête. À partir de là, vous pouvez mettre en place des alertes pour détecter les schémas critiques : une clé utilisée pour la première fois depuis des mois, un pic soudain du taux de requêtes, un pays d’origine qui ne correspond pas à l’intégration, ou une rafale d’erreurs 401 suggérant que quelqu’un tente de deviner les préfixes.
logger.info({
event: "api_key.used",
keyId: record.id,
ownerId: record.ownerId,
route: req.path,
ip: req.ip,
});
Ne loguez jamais la clé elle-même, seulement son id et son préfixe. Affichez l’utilisation dans le tableau de bord pour que les clients puissent voir quelles clés sont actives et repérer celles qu’ils ne reconnaissent pas. Envoyez un e-mail lors de la création, de la rotation et de la révocation, et donnez aux propriétaires un moyen de désactiver instantanément une clé suspecte.
Pour une approche plus large de la protection d’une API contre les abus, le guide sur le rate limiting traite des quotas, de la gestion des pics de trafic et de la manière dont les limites par clé s’articulent.
Politiques d’expiration et de cycle de vie
Une clé sans date d’expiration est une clé que vous oublierez jusqu’au jour où elle fuira. Attribuez un cycle de vie à chaque clé, même si le délai par défaut est généreux.
Une expiration optionnelle permet à un utilisateur de créer une clé qui expire à une date choisie, ce qui est idéal pour un prestataire, une intégration temporaire ou une migration ponctuelle. Une durée de vie maximale absolue limite la longévité de n’importe quelle clé, après quoi la rotation devient obligatoire. De nombreuses plateformes combinent les deux : les clés ont une durée par défaut d’un an, peuvent être plus courtes, mais ne peuvent jamais dépasser deux ans.
Suivez un état plutôt qu’un simple booléen. Une clé peut être active, en passe à expirer, expirée ou révoquée, et chaque état mérite une réponse différente. Les clés approchant de l’expiration devraient déclencher l’envoi d’un e-mail afin que le propriétaire effectue la rotation avant une interruption de service, et le middleware de vérification doit traiter les clés expirées et révoquées de la même manière : rejet avec une erreur 401.
function isUsable(key: ApiKey): boolean {
if (key.revokedAt) return false;
if (key.expiresAt && key.expiresAt < new Date()) return false;
return true;
}
L’expiration est un filet de sécurité, pas un substitut à la révocation. Une clé volée qui expire dans un an reste dangereuse ; la rotation et la surveillance demeurent donc les principaux contrôles. Cependant, une limite d’expiration signifie qu’une clé oubliée, ou celle d’un employé ayant quitté l’entreprise, finira par cesser de fonctionner d’elle-même.
Tester l’authentification par clé API
La vérification des clés API représente une petite quantité de code qui protège un accès considérable ; testez-la donc rigoureusement, en privilégiant les cas d’erreur (tests négatifs).
import request from "supertest";
import app from "../app.js";
test("rejects a request with no key", async () => {
await request(app).get("/v1/projects").expect(401);
});
test("rejects a malformed key", async () => {
await request(app)
.get("/v1/projects")
.set("Authorization", "Bearer not-a-real-key")
.expect(401);
});
test("rejects a revoked key", async () => {
const { key } = await createKey();
await revokeKey(key);
await request(app)
.get("/v1/projects")
.set("Authorization", `Bearer ${key}`)
.expect(401);
});
test("rejects a key without the required scope", async () => {
const { key } = await createKey({ scopes: ["projects:read"] });
await request(app)
.post("/v1/deployments")
.set("Authorization", `Bearer ${key}`)
.expect(403);
});
Ajoutez des tests prouvant que la clé complète n’est pas stockée : créez une clé, inspectez la ligne en base de données et vérifiez que key_hash est bien le hash et que le texte en clair n’apparaît nulle part. Vérifiez que last_used_at s’incrémente lors de l’utilisation. Testez également explicitement le chevauchement lors de la rotation : l’ancienne et la nouvelle clé doivent toutes deux fonctionner pendant la fenêtre de transition, et seule la nouvelle doit être valide une fois celle-ci fermée.
Enfin, vérifiez que les réponses d’erreur ne font pas de distinction entre un préfixe inconnu et un secret erroné. Les deux doivent retourner le même statut et le même corps de réponse, sinon vous offrez à un attaquant un moyen de confirmer quels préfixes existent.
Création de l’interface de gestion des clés
L’écran de gestion est l’endroit où les propriétés de sécurité deviennent visibles pour les utilisateurs ; il doit donc rendre le comportement recommandé le plus simple à adopter.
La vue en liste affiche pour chaque clé son nom, son préfixe, ses scopes, sa date de création et sa dernière utilisation, mais jamais le secret. Elle propose un bouton de révocation avec une confirmation, et fait de la “création de clé” le chemin privilégié pour la rotation. Le flux de création permet à l’utilisateur de choisir les scopes et une expiration optionnelle, puis affiche la clé complète une seule fois avec un bouton de copie et un avertissement sans équivoque.
sk_live_8f2a... CI deploy deploy:write created 3 days ago
sk_live_1c4b... Monitoring metrics:read last used 2 minutes ago
sk_test_9a7d... Staging projects:read revoked yesterday
Quelques détails utiles à inclure : un horodatage de “dernière utilisation” pour que les utilisateurs puissent repérer les clés qu’ils ne reconnaissent pas, une révocation en un clic pour une seule clé, une option “tout révoquer” pour le compte, et des notifications par e-mail à chaque création et révocation. Si vous affichez un graphique d’utilisation, faites-le par clé, car c’est l’unité de référence pour l’utilisateur.
Ne créez pas de bouton “révéler la clé”. Cela ne peut pas exister si vous utilisez un hachage correct, et son absence est une fonctionnalité : cela signifie qu’une fuite de base de données est surmontable. Expliquez dans l’interface que les clés ne sont affichées qu’une seule fois et que la rotation est la méthode de récupération, afin que cette contrainte paraisse délibérée et non comme un dysfonctionnement.
Choisir l’encodage et la longueur
Le choix de l’encodage est une décision mineure avec quelques conséquences pratiques. Le Base64url est le choix le plus courant car il est compact, sûr pour les URLs et les headers, et sensible à la casse, ce qui maximise l’entropie par caractère. L’hexadécimal (hex) est plus long, mais plus facile à lire à haute voix et à retrouver dans les logs. Le Base62 se situe entre les deux et évite totalement + et /.
| Encodage | 128 bits | 256 bits | Notes |
|---|---|---|---|
| base64url | 22 caract. | 43 caract. | Compact, sûr pour les URLs, sensible à la casse |
| hex | 32 caract. | 64 caract. | Plus long, facile à copier, insensible à la casse |
| base62 | 22 caract. | 43 caract. | Alphanumérique uniquement, sans symboles |
Quel que soit votre choix, gardez le secret sensible à la casse et ne le convertissez jamais en minuscules avant la comparaison. Un bug courant provient d’un middleware ou d’un proxy qui normalise les valeurs des headers, cassant ainsi silencieusement les clés contenant des majuscules. Documentez le format exact et rendez le préfixe suffisamment distinctif pour qu’une clé soit reconnaissable dans un ticket de support sans être utile à un attaquant.
Ne rendez pas la clé auto-descriptive au-delà du préfixe. Intégrer l’ID utilisateur, une somme de contrôle (checksum) ou une date d’expiration à l’intérieur de la clé peut vous tenter de sauter la requête en base de données, mais cela signifie également que la clé transporte des informations et ne peut de toute façon pas être révoquée sans une vérification. Gardez le secret opaque et laissez la base de données gérer la sémantique.
Stocker les secrets client en toute sécurité
Le stockage côté serveur n’est qu’une partie de l’équation ; le client doit également garder la clé en sécurité. Fournissez aux intégrateurs des conseils clairs et tranchés, car le réflexe de nombreux développeurs est de coller une clé dans un fichier et de l’envoyer via un commit.
Recommandez l’utilisation de variables d’environnement pour les serveurs, injectées par la plateforme ou un gestionnaire de secrets plutôt que d’être écrites dans un .env versionné. En CI, utilisez le magasin de secrets du pipeline et masquez la valeur dans les logs. Pour le développement local, chargez-les depuis un .env ignoré par Git, et privilégiez des clés sk_test_ afin qu’une erreur n’affecte que les données de sandbox.
# Load from the environment, never hard-code.
export MYAPP_API_KEY="sk_live_8f2a9c..."
curl -H "Authorization: Bearer $MYAPP_API_KEY" https://api.example.com/v1/projects
Orientez les utilisateurs vers des gestionnaires de secrets managés — AWS Secrets Manager, Google Secret Manager, Vault, ou les variables intégrées de la plateforme — et expliquez la rotation des clés avec des instructions concrètes. Si votre SDK lit la clé depuis une variable d’environnement par défaut, la plupart des intégrations adopteront le bon comportement sans même avoir besoin d’instructions, ce qui constitue le meilleur type de contrôle de sécurité.
Clés secrètes et clés publiables
De nombreuses plateformes fournissent deux types de clés, et les confondre peut causer de graves problèmes. Une clé secrète authentifie un serveur de confiance et ne doit jamais être exposée. Une clé publiable est destinée à être intégrée dans le code client et peut être rendue publique sans risque, car elle ne confère aucun privilège par elle-même.
sk_live_... secret, server-only, carries scopes and a quota
pk_live_... publishable, client-safe, identifies the account only
Une clé publiable est utile pour l’attribution et la limitation du débit (rate limiting) dans le navigateur, mais chaque opération privilégiée doit toujours être autorisée par un élément que le client ne peut pas falsifier : un jeton à courte durée de vie généré par votre serveur, ou une session utilisateur. Considérez la clé publiable comme un identifiant et non comme un identifiant d’authentification, et ne laissez jamais sa seule présence accorder un accès.
Nommer ces deux clés de manière cohérente rend la distinction évidente au premier coup d’œil et permet aux scanners de secrets, aux revues de code et à la documentation de renforcer la même règle. Si un développeur colle par erreur une clé sk_ dans le code front-end, le préfixe seul devrait rendre l’erreur visible avant le déploiement.
Bonnes pratiques
- Générez le secret à partir d’un CSPRNG avec au moins 128 bits d’entropie, préfixé pour faciliter la lecture et l’identification de l’environnement.
- Ne stockez qu’un hash SHA-256 et un court préfixe ; ne persistez jamais la clé complète.
- Affichez la clé complète une seule fois, lors de sa création, et faites de la rotation le moyen de récupération.
- Comparez les hashs en temps constant et retournez la même erreur pour les clés inconnues et invalides.
- Indexez le préfixe afin que la vérification ne nécessite qu’une seule recherche et un seul hash.
- Limitez chaque clé aux permissions minimales et associez-lui une limite de débit (rate limit) spécifique.
- Encodez l’environnement dans le préfixe et rejetez une clé de sandbox sur l’API de production.
- Acceptez les clés dans le header
Authorization, jamais dans une query string. - Supportez la rotation sans interruption de service (zero-downtime) avec une fenêtre de chevauchement, et rendez la révocation immédiate.
- Surveillez
last_used_at, alertez en cas d’anomalies et notifiez les propriétaires à chaque modification d’identifiants. - Séparez les clés secrètes des clés publiables, et ne permettez jamais à une clé publiable d’accorder un accès par elle-même.
- Documentez le format des clés, les scopes et la politique de rotation pour que les intégrateurs puissent s’y conformer sans tâtonner.
Erreurs courantes
- Stocker des clés en texte brut en supposant que la base de données ne fuitera jamais.
- Hasher chaque ligne pour trouver une correspondance au lieu d’indexer un préfixe.
- Utiliser
Math.randomou un UUID comme secret, ce qui réduit l’espace de recherche. - Intégrer une clé à longue durée de vie dans un bundle navigateur, une application mobile ou un binaire desktop.
- Placer la clé dans une query string, où les logs et l’historique peuvent la capturer.
- Accorder un accès complet à chaque clé parce que la définition de scopes semblait être un travail superflu.
- Ne jamais faire de rotation ou définir d’expiration pour les clés, rendant une fuite utile indéfiniment.
- Supprimer les lignes révoquées et perdre ainsi la piste d’audit.
- Logger l’intégralité des headers ou des URLs, faisant fuiter des clés dans les outils d’observabilité.
- Retourner une erreur différente pour un préfixe inconnu que pour un secret erroné.
Et après ?
Les clés API sont les identifiants les plus simples de votre boîte à outils, et les mêmes réflexes — hachage au repos, limitation stricte des scopes, rotation à la demande — s’appliquent partout. Si vous avez besoin de jetons vérifiables à courte durée de vie contenant des revendications (claims), consultez le guide sur les JWT. Lorsque l’appelant agit au nom d’un utilisateur et nécessite son consentement, OAuth 2.0 est le modèle approprié. Les chaînes de permission d’une clé sont les mêmes composants utilisés par le RBAC, ce guide est donc le compagnon idéal pour gérer le scoping. Enfin, comme les clés sont un support naturel pour les quotas, la section sur le rate limiting explique comment empêcher une seule intégration d’épuiser la capacité pour tout le monde.