L’authentification API sert à vérifier qui appelle avant d’ouvrir l’accès. Le vrai sujet, c’est le compromis entre sécurité, simplicité et exploitation. Je vous montre quand utiliser API keys, Basic Auth, mTLS, HMAC, OAuth 2.0, JWT ou OpenID Connect, sans empiler de la complexité inutile.
Authentification ou autorisation ?
Quand je sécurise une API, je sépare toujours deux questions qui se mélangent trop souvent. L’authentification répond à “qui appelle ?”. L’autorisation répond à “qu’est-ce que cette identité a le droit de faire ?”. Ça paraît évident, mais c’est là que beaucoup de failles commencent.
Une requête API peut arriver avec un jeton parfaitement valide. Par exemple un token OAuth 2.0, un standard très utilisé pour déléguer des accès sans partager de mot de passe. L’API vérifie la signature, la date d’expiration, l’émetteur du jeton. Tout est bon. L’appelant est bien identifié.
GET /clients/42
Authorization: Bearer eyJhbGciOi...
# Le jeton est valide.
# Mais est-ce que cette application a le droit de lire ce client précis ?
Le vrai sujet arrive juste après. Si ce jeton donne accès à tout le module client, lecture et modification incluses, on a un problème. L’application est authentifiée correctement, mais elle est autorisée trop largement. J’ai déjà vu ça chez un client avec une application partenaire censée seulement consulter des commandes. Le token permettait aussi de modifier les statuts. Pas par malveillance au départ, juste parce que personne n’avait séparé identité et permissions.
Dans une API REST, on doit raisonner avec plusieurs éléments simples. L’identité, c’est l’application, l’utilisateur ou le service qui appelle. Le périmètre d’accès, souvent appelé scope, décrit une zone fonctionnelle comme “read:customers” ou “write:orders”. Les permissions sont les droits réels appliqués côté serveur. La frontière de confiance, c’est l’endroit où l’API arrête de croire aveuglément ce qui vient de l’extérieur.
Les bonnes pratiques OWASP sur la sécurité API insistent beaucoup là-dessus. Une API ne doit pas seulement vérifier qu’un token existe. Elle doit vérifier que l’action demandée est autorisée pour cette identité, sur cette ressource, dans ce contexte. Lire une fiche client n’est pas modifier son adresse. Voir ses propres données n’est pas voir celles de toute l’entreprise.
| Authentification | Prouve qui appelle l’API. |
| Autorisation | Décide ce que cette identité peut faire. |
| Erreur fréquente | Accepter un token valide sans contrôler les droits précis. |
| Bon réflexe | Limiter les accès par action, ressource et contexte. |
Quand utiliser une API key ?
J’utilise une API key quand je veux identifier une application, pas une personne. Typiquement, un service interne qui appelle un autre service, une API publique avec des quotas, ou un client serveur qui a besoin d’un accès simple. La clé est une chaîne unique, envoyée à chaque requête. Je la préfère dans un en-tête HTTP, souvent x-api-key, plutôt que dans l’URL. Une clé dans l’URL finit trop vite dans les logs, l’historique navigateur, les outils d’analytics, bref là où elle ne devrait pas être.
Le vrai avantage, c’est la simplicité. C’est rapide à intégrer, facile à mesurer, pratique pour limiter l’usage, appliquer des quotas, ou couper l’accès d’un client précis. J’ai vu ça marcher très bien sur des API partenaires assez simples. Pas besoin de sortir OAuth2 si le besoin réel, c’est juste “quelle application m’appelle ?”.
Le défaut est clair : si la clé fuite, quelqu’un peut se faire passer pour l’application jusqu’à révocation ou rotation. Le risque explose si la clé vit trop longtemps, si elle donne accès à trop de choses, ou si elle est stockée côté front. Une API key dans du JavaScript navigateur, c’est public. Même minifiée, même “cachée”.
const express = require("express");
const app = express();
// Stocke la clé côté serveur, jamais en dur dans le code.
const expectedApiKey = process.env.API_KEY;
app.get("/data", (req, res) => {
// Récupère la clé envoyée dans l'en-tête HTTP x-api-key.
const apiKey = req.header("x-api-key");
// Refuse la requête si la clé manque ou ne correspond pas.
if (!apiKey || apiKey !== expectedApiKey) {
return res.status(401).json({ error: "API key manquante ou invalide" });
}
// Continue seulement si l'application est identifiée.
res.json({ message: "Accès autorisé" });
});
app.listen(3000, () => {
console.log("API disponible sur http://localhost:3000");
});
curl -H "x-api-key: ma-cle-api-secrete" http://localhost:3000/data
Ma checklist courte avant de partir en production :
- Ne jamais mettre la clé dans le front ou une app mobile non protégée.
- Activer une rotation régulière des clés.
- Limiter les scopes si possible, c’est-à-dire les droits accordés à la clé.
- Journaliser l’usage pour repérer les abus.
- Prévoir révocation, quotas et alertes dès le départ.
Basic Auth a encore sa place ?
Basic Auth a encore sa place, oui. Mais pas partout. Je l’utilise encore sur des intégrations internes, des scripts serveur à serveur, ou des vieux systèmes qu’on ne va pas réécrire juste pour faire joli. La condition non négociable, c’est HTTPS.
Le principe vient de HTTP Basic Authentication, défini dans la RFC 7617. Le client envoie un en-tête Authorization avec un couple username:password encodé en Base64. Et Base64, attention, ce n’est pas du chiffrement. C’est juste un encodage réversible. Si quelqu’un intercepte la requête sans HTTPS, il récupère les identifiants.
Son intérêt est simple :
- Configuration rapide.
- Supporté presque partout.
- Très peu de logique côté client.
- Pratique pour un job cron, un webhook interne, un outil d’admin bien isolé.
Le revers, c’est que les identifiants partent à chaque requête. Ils restent valables tant qu’on ne les change pas. Et si vous les stockez en dur dans un dépôt Git, dans un vieux fichier partagé ou dans des logs, ça devient vite un problème. J’ai déjà vu un client exposer un endpoint interne comme ça “temporairement”. Six mois plus tard, il était toujours ouvert.
import express from "express";
import "dotenv/config";
const app = express();
function deny(res) {
// On demande au client de fournir une authentification Basic.
res.set("WWW-Authenticate", 'Basic realm="internal-api"');
return res.status(401).json({ error: "Unauthorized" });
}
function basicAuth(req, res, next) {
// Exemple attendu : Authorization: Basic dXNlcjpwYXNz
const header = req.headers.authorization || "";
if (!header.startsWith("Basic ")) {
return deny(res);
}
// On récupère puis décode la partie Base64.
const encoded = header.slice("Basic ".length);
const decoded = Buffer.from(encoded, "base64").toString("utf8");
// Le format doit être username:password.
const separator = decoded.indexOf(":");
if (separator === -1) {
return deny(res);
}
const username = decoded.slice(0, separator);
const password = decoded.slice(separator + 1);
// Les secrets viennent des variables d'environnement.
if (
username === process.env.API_USER &&
password === process.env.API_PASSWORD
) {
return next();
}
return deny(res);
}
app.get("/private", basicAuth, (req, res) => {
res.json({ ok: true });
});
app.listen(3000, () => {
console.log("API listening on port 3000");
});
API_USER=admin API_PASSWORD=secret node server.js
curl -u admin:secret https://api.example.com/private
Je ne choisirais pas Basic Auth pour une application mobile, un navigateur, un accès utilisateur moderne, une API publique exposée largement, ou un besoin de délégation fine. Là, je pars plutôt sur OAuth2, OpenID Connect ou des tokens mieux cadrés.
mTLS ou HMAC pour sécuriser plus fort ?
Quand une clé API devient trop faible, je passe sur une preuve cryptographique. Là, les deux options que je vois le plus souvent sont mTLS et HMAC. Les deux sécurisent mieux, mais pas de la même manière.
mTLS, pour mutual TLS, c’est TLS dans les deux sens. Le serveur présente son certificat au client, comme en HTTPS classique, et le client présente aussi son certificat au serveur. Ça donne une authentification service à service très solide. C’est le genre de mécanique que j’aime bien pour des flux internes critiques, des partenaires B2B, ou des APIs exposées entre systèmes sensibles. C’est aussi utilisé avec OAuth, notamment dans la RFC 8705, pour lier un token à un certificat client.
Le revers, c’est l’exploitation. Il faut générer les certificats, les distribuer, les renouveler, les révoquer proprement. Et quand un certificat expire un dimanche soir, bizarrement tout le monde redécouvre la PKI. Je l’ai vu chez un client, l’auth était excellente, mais personne n’avait automatisé la rotation. Résultat, outage évitable.
HMAC, c’est différent. Le client et le serveur partagent un secret. Le client signe la requête avec ce secret, souvent à partir de la méthode HTTP, de l’URL, du timestamp et du body. Le serveur reconstruit exactement la même chaîne, recalcule la signature, et refuse si ça ne matche pas. Ça protège contre la falsification, et contre le rejeu si on impose un timestamp court, par exemple 5 minutes, avec éventuellement un nonce, c’est-à-dire un identifiant unique de requête déjà consommé.
const express = require('express');
const crypto = require('crypto');
const app = express();
const SHARED_SECRET = process.env.HMAC_SECRET || 'secret-dev';
const MAX_SKEW_MS = 5 * 60 * 1000;
// Je garde le body brut, sinon la signature peut changer après parsing JSON.
app.use(express.raw({ type: '*/*' }));
app.post('/api/orders', (req, res) => {
const signature = req.header('x-signature');
const timestamp = req.header('x-timestamp');
if (!signature || !timestamp) {
return res.status(401).json({ error: 'Signature ou timestamp manquant' });
}
const timestampMs = Number(timestamp);
const now = Date.now();
if (!Number.isFinite(timestampMs) || Math.abs(now - timestampMs) > MAX_SKEW_MS) {
return res.status(401).json({ error: 'Timestamp expiré ou invalide' });
}
const rawBody = req.body.toString('utf8');
// La chaîne doit être reconstruite exactement pareil côté client et serveur.
const stringToSign = [
req.method.toUpperCase(),
req.originalUrl,
timestamp,
rawBody
].join('\n');
const expectedSignature = crypto
.createHmac('sha256', SHARED_SECRET)
.update(stringToSign)
.digest('hex');
const received = Buffer.from(signature, 'hex');
const expected = Buffer.from(expectedSignature, 'hex');
if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
return res.status(401).json({ error: 'Signature invalide' });
}
return res.json({ ok: true });
});
app.listen(3000, () => {
console.log('API HMAC prête sur http://localhost:3000');
});
const crypto = require('crypto');
const SHARED_SECRET = process.env.HMAC_SECRET || 'secret-dev';
const method = 'POST';
const url = 'http://localhost:3000/api/orders';
const path = '/api/orders';
const timestamp = Date.now().toString();
const body = JSON.stringify({ orderId: 123, amount: 49.9 });
const stringToSign = [
method,
path,
timestamp,
body
].join('\n');
const signature = crypto
.createHmac('sha256', SHARED_SECRET)
.update(stringToSign)
.digest('hex');
fetch(url, {
method,
headers: {
'content-type': 'application/json',
'x-timestamp': timestamp,
'x-signature': signature
},
body
})
.then(res => res.json())
.then(console.log);
| Critère | mTLS | HMAC |
| Niveau d’assurance | Très fort, basé sur certificats client et serveur. | Fort si le secret reste privé et si la signature est bien construite. |
| Complexité | Élevée, surtout à cause de la PKI et de la rotation. | Moyenne, plus simple à déployer dans une API classique. |
| Cas d’usage | Service à service, banque, santé, partenaires critiques. | Webhooks, APIs privées, intégrations serveur à serveur. |
| Rotation | Rotation de certificats, révocation, dates d’expiration. | Rotation de secrets partagés, souvent plus simple. |
| Erreurs fréquentes | Certificat expiré, chaîne incomplète, révocation oubliée. | Body modifié, timestamp absent, comparaison non sécurisée. |
OAuth JWT ou OpenID Connect ?
OAuth 2.0, JWT et OpenID Connect ne jouent pas le même rôle. OAuth 2.0 sert surtout à déléguer l’accès. JWT sert souvent à transporter des claims, c’est-à-dire des informations vérifiables comme un user_id, une audience ou des scopes. OpenID Connect ajoute l’identité utilisateur au-dessus d’OAuth 2.0, avec un ID token.
OAuth 2.0, défini par RFC 6749, ne dit pas à lui seul qui est l’utilisateur. Il dit plutôt “ce client a le droit d’appeler cette API avec tel niveau d’accès”. OpenID Connect répond à la question “qui est connecté ?”. JWT, défini par RFC 7519, est juste un format de jeton signé. Pas une stratégie complète. J’ai vu pas mal d’équipes dire “on fait du JWT” alors qu’en vrai elles avaient inventé un mini OAuth bancal.
- OAuth 2.0 est adapté quand vous avez des accès délégués, des scopes, des clients tiers ou plusieurs applications.
- JWT est utile quand plusieurs services doivent vérifier un token sans rappeler une base centrale à chaque requête.
- OpenID Connect est le bon choix quand il faut connecter un utilisateur et récupérer son identité proprement.
Le compromis est simple. C’est plus robuste, plus standard, plus interopérable. C’est aussi plus complexe qu’une API key.
import express from "express";
import { expressjwt } from "express-jwt";
import jwksRsa from "jwks-rsa";
const app = express();
const issuer = process.env.OIDC_ISSUER; // Exemple: https://auth.example.com/
const audience = process.env.API_AUDIENCE; // Exemple: https://api.example.com
const checkJwt = expressjwt({
// Récupère la clé publique depuis le JWKS du fournisseur d'identité.
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${issuer}.well-known/jwks.json`
}),
issuer,
audience,
algorithms: ["RS256"] // Ne jamais accepter "none".
});
function requireScope(requiredScope) {
return (req, res, next) => {
const scopes = (req.auth?.scope || "").split(" ");
if (!scopes.includes(requiredScope)) {
return res.status(403).json({ error: "insufficient_scope" });
}
next();
};
}
app.get("/orders", checkJwt, requireScope("orders:read"), (req, res) => {
res.json([{ id: "ord_123", total: 99 }]);
});
app.use((err, req, res, next) => {
if (err.name === "UnauthorizedError") {
return res.status(401).json({ error: "invalid_token" });
}
next(err);
});
app.listen(3000);
Je garde trois règles en tête. Ne jamais accepter alg none. Ne pas confondre access token et ID token, l’ID token sert au client, pas à protéger une API. Ne pas stocker des secrets ou des clés privées en dur dans le code.
| Méthode | Usage recommandé | Compromis principal |
| API keys | Accès simple entre serveur et API | Facile à voler et peu granulaire |
| Basic Auth | Cas internes très simples avec TLS | Identifiants envoyés à chaque appel |
| mTLS | Machine à machine très sensible | Gestion des certificats lourde |
| HMAC | Webhooks et requêtes signées | Synchronisation et canonicalisation délicates |
| OAuth 2.0 | Délégation d’accès avec scopes | Mise en place plus complexe |
| JWT | Tokens signés vérifiables par plusieurs services | Révocation moins simple |
| OpenID Connect | Connexion utilisateur et identité standardisée | Dépendance à un fournisseur d’identité |
Alors je choisis quoi pour mon API ?
Je choisis une méthode d’authentification API en partant de trois questions simples : qui appelle, où est la frontière de confiance, et qu’est-ce qu’une fuite peut exposer. Pour une intégration simple entre applications, une API key bien gérée peut suffire. Pour de l’interne hérité, Basic Auth reste possible avec HTTPS. Pour du service à service critique, mTLS ou HMAC montent le niveau. Pour la délégation et les utilisateurs, OAuth 2.0, JWT et OpenID Connect sont plus adaptés. Le bénéfice pour vous est clair : sécuriser votre API sans ajouter une usine à gaz inutile.
FAQ
- Quelle est la différence entre authentification API et autorisation API ?
L’authentification vérifie qui appelle l’API. L’autorisation décide ce que cette identité peut faire. Un token peut prouver qu’une application est bien connue, mais les scopes ou permissions disent si elle peut lire, écrire, supprimer ou administrer une ressource. - Une API key est-elle suffisante pour sécuriser une API ?
Elle peut suffire pour identifier une application et gérer des quotas, surtout sur des cas simples. Mais elle devient risquée si elle donne accès à trop de ressources ou si elle vit trop longtemps. Je recommande toujours rotation, révocation, stockage serveur et périmètre limité. - Basic Auth est-il dangereux ?
Basic Auth n’est pas forcément dangereux, mais il doit être utilisé avec HTTPS. Le mot de passe est seulement encodé en Base64, pas chiffré. Je le garde plutôt pour des intégrations internes, des systèmes hérités ou des contextes très maîtrisés. - Quand choisir mTLS plutôt qu’une API key ?
Je choisis mTLS quand l’identité du service appelant doit être prouvée fortement au niveau de la connexion. C’est pertinent pour du service à service sensible. Le prix à payer, c’est la gestion des certificats, leur renouvellement et leur révocation. - OAuth 2.0, JWT et OpenID Connect font-ils la même chose ?
Non. OAuth 2.0 organise la délégation d’accès. JWT est un format de jeton signé qui transporte des informations. OpenID Connect ajoute l’authentification utilisateur au-dessus d’OAuth 2.0. Les trois peuvent travailler ensemble, mais ils ne répondent pas exactement au même besoin.
A propos de l’auteur
Je suis Franck Scandolera, expert et formateur en tracking avancé server-side, Analytics Engineering, automatisation No/Low Code avec n8n, intégration de l’IA en entreprise et SEO/GEO. Avec mon agence webAnalyste et l’organisme Formations Analytics, j’accompagne des équipes comme Logis Hôtel, Yelloh Village, BazarChic, la Fédération Française de Football ou Texdecor sur des sujets data, API, sécurité de tracking et automatisation. Si vous voulez sécuriser vos flux API ou fiabiliser vos intégrations business, contactez-moi, je peux vous aider.
⭐ Data Analyst, Analytics Engineer et expert dans l’automatisation IA ⭐
Ref clients : Logis Hôtel, Yelloh Village, BazarChic, Fédération Football Français, Texdecor…
Mon terrain de jeu :
Data Analyst & Analytics engineering : tracking propre RGPD, entrepôt de données (GTM server, BigQuery…), modèles (dbt/Dataform), dashboards décisionnels (Looker, SQL, Python).
Automatisation IA des taches Data, Marketing, RH, compta etc : conception de workflows intelligents robustes (n8n, Make, App Script, scraping) connectés aux API de vos outils et LLM (OpenAI, Mistral, Claude…).
Engineering IA pour créer des applications et agent IA sur mesure : intégration de LLM (OpenAI, Mistral…), RAG, assistants métier, génération de documents complexes, APIs, backends Node.js/Python.





