Skip to Content
BackendAuthentification API

Authentification API

Stratégies, implémentation et pièges pour sécuriser les endpoints d’une API HTTP.

Vue d’ensemble

StratégieCas d’usageStockage clientExpiration typique
JWTAPIs stateless, microservicesLocalStorage ou httpOnly cookieConfigurée (ex. 15 min)
Session cookieApplications web classiquesCookie httpOnlyInactive ou 24 h
API keyMachine-à-machine, CLI, webhooksVariable d’environnementIllimitée (rotation manuelle)
OAuth 2.0Authentification tierce (Google, GitHub)Token en mémoire (browser)Configurée

Choix rapide : JWT pour APIs stateless, session pour apps web avec navigateur, API key pour les services qui s’authentifient entre eux.

JWT — JSON Web Token

Un JWT est un token signé en trois parties : header.payload.signature.

Structure

// Header { "alg": "HS256", "typ": "JWT" } // Payload (claim) { "sub": "42", "iat": 1720000000, "exp": 1720003600, "role": "admin" }
  • Header : algorithme de signature (HS256, RS256) et type.
  • Payload : claims (sujet, expiration, rôles). Non chiffré — ne jamais y placer de donnée sensible.
  • Signature : garantit l’intégrité. Vérifiée avec la clé secrète.

Création (Node.js / Express)

const jwt = require('jsonwebtoken'); const payload = { sub: user.id, role: user.role }; const secret = process.env.JWT_SECRET; const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' }); const refreshToken = jwt.sign(payload, secret, { expiresIn: '7d' });

Vérification

const decoded = jwt.verify(token, secret); // decoded = { sub: '42', iat: 1720000000, exp: 1720003600, role: 'admin' } if (decoded.exp < Math.floor(Date.now() / 1000)) { throw new Error('Token expiré'); }

Refresh Token — Renouvellement sans reconnexion

Le flux standard utilise deux tokens :

// 1. Le client envoie le refreshToken au endpoint /refresh const decoded = jwt.verify(refreshToken, refreshSecret); // 2. Vérifier que le token est encore valide et non révoqué (base de données) if (!await isRefreshTokenValid(decoded.jti)) { throw new Error('Refresh token révoqué'); } // 3. Émettre un nouvel accessToken const newAccessToken = jwt.sign( { sub: decoded.sub, role: decoded.role }, accessSecret, { expiresIn: '15m' } ); // 4. Révoquer l'ancien refreshToken et en créer un nouveau await revokeRefreshToken(decoded.jti); const newRefreshToken = jwt.sign( { sub: decoded.sub, jti: crypto.randomUUID() }, refreshSecret, { expiresIn: '7d' } );

Piège : un access token expiré ne doit jamais être renvoyé au client sans un refresh. Vérifiez toujours exp côté serveur à chaque requête protégée.

Stockage côté client

EmplacementSécuritésRisque
httpOnly cookieopaque au JS, serveur-only accessXSS ne peut pas le voler
localStorageaccessible au JSVulnérable au XSS
sessionStorageporté sur la session du navigateurPertes après fermeture d’onglet

Recommandation : pour une API stateless consommée par un SPA, préférer un cookie httpOnly avec un token anti-CSRF (double submit ou SameSite=Strict).

Clés API

Utiliser une clé API quand l’authentifié est un service (pas un humain). Pas de flow d’authentification, juste un credential statique.

Quand utiliser une clé API

  • Appels server-à-server (webhooks, workers, CI/CD)
  • Accès programmatique via CLI
  • Intégrations tierces (autres entreprises, partenaires)

Rotation des clés

# Exemple : générer une clé API via la CLI curl -X POST https://api.exemple.com/v1/api-keys \ -H "Authorization: Bearer eyJhbG...9... \ -H "Content-Type: application/json" \ -d '{"name": "worker-production", "scopes": ["read:posts", "write:posts"]}' # Réponse { "id": "ak_7f8a9b0c1d2e", "key": "sk_liv...7j8k", "name": "worker-production", "scopes": ["read:posts", "write:posts"], "created_at": "2025-07-04T10:00:00Z" }

Préfixes de clés (notation conventionnelle)

PréfixeUsageEnvironnement
sk_live_Clé de signatureProduction
sk_test_Clé de testSandbox
rk_live_Clé de lectureProduction (lecture seule)
pk_live_Clé publiqueFront-end (limitée)

Règle : une clé en production ne doit jamais apparaître dans les logs. Masquer les 4 premiers caractères et les 4 derniers : sk_live_****....3f4g5h6i7j8k.

OAuth 2.0 / OpenID Connect

Flux Client Credentials (machine-à-machine)

Le flux le plus simple pour OAuth 2.0. Aucun utilisateur final impliqué.

curl -X POST https://auth.exemple.com/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=app_client_id" \ -d "client_secret=app_client_secret" \ -d "scope=read:users write:users" # Réponse { "access_token": "eyJhbGciOi...", "token_type": "Bearer", "expires_in": 3600, "scope": "read:users write:users" }

Rôle de l’ID Token (OpenID Connect)

OpenID Connect ajoute un calque au-dessus d’OAuth 2.0 pour fournir des informations sur l’utilisateur. L’id_token est un JWT signé contenant :

{ "sub": "user-42", "name": "Arthur Dupont", "email": "arthur@example.com", "iss": "https://auth.exemple.com", "aud": "app_client_id", "exp": 1720003600, "iat": 1720000000 }

Piège : ne pas confondre access_token (utilisé pour accéder aux ressources) et id_token (utilisé pour identifier l’utilisateur). Les deux peuvent être différents.

Headers d’authentification

Schéma Authorization: Bearer

Le standard HTTP pour transmettre un token :

GET /api/users/me HTTP/1.1 Host: api.exemple.com Authorization: Bearer eyJhbG...9...

Autres schémas courants

SchémaUsageExemple
BearerJWT, OAuth tokensAuthorization: Bearer eyJhbG...
BasicUsername/password encodé en base64Authorization: Basic dXNlcjpwYXNz
Token (custom)API keys simplesAuthorization: Token sk_live_4f8a...

Convention : préférer Bearer pour les tokens JWT/OAuth et Token pour les API keys. Les deux fonctionnent mais un choix cohérent simplifie le débogage.

Dépréciation de Basic

// ❌ À éviter en production — les credentials transitaient en base64 (pas chiffrés) Authorization: Basic YXJ0aHVyOnNlY3JldDEyMw==

Piège : Basic n’est pas « sécurisé » — le base64 est trivialment décodable. Toujours utiliser HTTPS avec Basic, ou mieux, remplacer par un schéma plus robuste.

Middleware d’authentification

Pattern Express (Node.js)

function authMiddleware(req, res, next) { const header = req.headers.authorization; if (!header || !header.startsWith('Bearer ')) { return res.status(401).json({ error: { code: 'MISSING_TOKEN', message: 'Token absent.' } }); } const token = header.slice(7); try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (err) { if (err.name === 'TokenExpiredError') { return res.status(401).json({ error: { code: 'TOKEN_EXPIRED', message: 'Token expiré.' } }); } return res.status(401).json({ error: { code: 'INVALID_TOKEN', message: 'Token invalide.' } }); } } // Usage app.get('/api/users/me', authMiddleware, (req, res) => { res.json({ user: req.user }); });

Pattern Laravel (PHP)

// app/Http/Middleware/ApiTokenAuth.php <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class ApiTokenAuth { public function handle(Request $request, Closure $next) { $token = $request->bearerToken(); if (!$token) { return response()->json([ 'error' => ['code' => 'MISSING_TOKEN', 'message' => 'Token absent.'] ], 401); } $user = Auth::guard('api')->user(); if (!$user) { return response()->json([ 'error' => ['code' => 'INVALID_TOKEN', 'message' => 'Token invalide.'] ], 401); } $request->setUserResolver(function () use ($user) { return $user; }); return $next($request); } }

Registre dans app/Http/Kernel.php :

'api' => [ 'throttle:api', \App\Http\Middleware\ApiTokenAuth::class, ],

Recommandation : un middleware d’authentification doit être injactable (@requires auth) et retourner un 401 standardisé avec un code erreur machine.

Pièges courants

JWT sans expiration

// ❌ MAUVAIS — le token est valide indéfiniment jwt.sign(payload, secret);

Piège : un JWT sans exp est valide à vie. Si le secret fuit, l’accès compromis est permanent. Toujours définir une expiration courte pour l’access_token (15 min).

Tokens dans les logs

// ❌ MAUVAIS — le token est imprimé en clair dans les logs app.use((req, res, next) => { console.log(`Requête ${req.method} ${req.url} — Token: ${req.headers.authorization}`); next(); });

Piège : les tokens dans les logs peuvent être compromis si un accès aux logs est obtenu (ELK, Datadog, CloudWatch). Masquer systématiquement les credentials.

Horodatage serveur vs client

Ne jamais faire confiance à l’horloge du client pour vérifier une expiration. Toujours utiliser l’heure serveur :

// ❌ MAUVAIS — le client peut bidouiller son horloge const now = new Date(); // heure client // ✅ BON — heure serveur const now = Math.floor(Date.now() / 1000); const exp = decoded.exp; if (exp < now) { throw new Error('Token expiré'); }

Piège : un utilisateur malveillant peut avancer son horloge pour contourner l’expiration. Côté serveur, utiliser Date.now() ou l’horloge du serveur.

Renouvellement silent

Si un access_token expire pendant qu’une SPA est ouverte, ne pas rediriger brutalement vers la page de connexion. Le renouvellement silent (via un endpoint /refresh) préserve l’expérience utilisateur.

Checklist d’implémentation

  • Schéma d’authentification choisi (JWT, session, API key, OAuth)
  • Middleware d’authentification unitaire et testé
  • Expiration configurée (exp claim) sur tous les tokens
  • Refresh token réversible (stocké en base, révoqué après usage)
  • Headers de token masqués dans les logs
  • Code d’erreur standardisé (MISSING_TOKEN, INVALID_TOKEN, TOKEN_EXPIRED)
  • Vérification de l’heure serveur (pas client)
  • Rotation des clés API planifiée et documentée
  • HTTPS forcé pour tous les endpoints authentifiés