Application Security

Durcir vos APIs : Guide pratique pour implémenter le liaison de jetons OAuth2 et DPoP

OAuth 2.0 a révolutionné la manière dont nous autorisons l'accès aux ressources web, mais il a introduit une vulnérabilité de sécurité significative : le vol de jetons. Si un attaquant obtient un jeton d'accès valide, il peut souvent l'utiliser pour usurper l'identité de l'utilisateur jusqu'à expiration du jeton, que celui-ci ait été transmis ou non via le canal sécurisé d'origine. Bien que la sécurité de la couche de transport (TLS) protège les jetons en transit, elle ne les lie pas au client spécifique auquel ils ont été émis. C'est ici qu'intervient la Délégation de Preuve de Possession (DPoP).

DPoP, standardisé dans la RFC 9449, étend OAuth 2.0 en garantissant que les jetons d'accès sont cryptographiquement liés à la clé publique du client qui les détient. Cet article de blog explorera le fonctionnement de DPoP et fournira un guide d'implémentation pratique pour les développeurs intermédiaires à avancés souhaitant renforcer la posture de sécurité de leurs APIs.

Comprendre le mécanisme DPoP

Le concept central de DPoP est simple mais puissant : le client doit prouver qu'il possède la clé privée correspondant à la clé publique envoyée dans la demande. Au lieu de simplement envoyer le jeton, le client crée un Jeton Web JSON (JWT) signé avec sa clé privée. Ce JWT, appelé preuve DPoP, est envoyé dans un en-tête spécial conjointement avec le jeton d'accès.

Les composants clés du flux DPoP incluent :

  • Génération de clé DPoP : Le client génère une paire de clés (généralement Ed25519 ou RSA).
  • Création de la preuve DPoP : Un JWT contenant la clé publique, l'horodatage actuel et l'URL du jeton est signé.
  • Injection d'en-tête : La preuve est envoyée dans l'en-tête DPoP, et l'empreinte digitale de la clé publique est envoyée dans l'en-tête DPOP.
  • Validation côté serveur : Le serveur de ressources valide la signature, vérifie les horodatages et s'assure que le jeton a été utilisé sur le même point de terminaison.

Implémentation côté client en Node.js

Examinons un exemple pratique utilisant Node.js et la bibliothèque populaire axios. Tout d'abord, vous avez besoin d'une bibliothèque qui gère la création de JWT et la signature cryptographique. Pour cet exemple, nous supposerons l'utilisation de jose pour les opérations JWT.

Voici comment vous pouvez générer une preuve DPoP et l'attacher à une requête API :

const jose = require('jose');
const axios = require('axios');

async function makeDPOPRequest() {
  // 1. Générer une paire de clés (Ed25519 est recommandé)
  const { publicKey, privateKey } = await jose.generateKeyPair('EdDSA');
  
  // 2. Créer l'en-tête JWT DPoP
  const publicKeyJWK = await jose.exportJWK(publicKey);
  const dpopHeader = {
    typ: 'dpop+jwt',
    alg: 'EdDSA',
    jwk: publicKeyJWK
  };

  // 3. Créer la charge utile JWT DPoP
  const url = 'https://api.example.com/resource';
  const now = Math.floor(Date.now() / 1000);
  const payload = {
    iss: 'client_id',
    jti: crypto.randomUUID(),
    aud: url, // Le point de terminaison de l'API
    iat: now,
    exp: now + 60 // De courte durée, généralement 60 secondes
  };

  // 4. Signer le JWT avec la clé privée
  const signer = await jose.importJWK(publicKeyJWK, 'EdDSA'); // Note : jose gère la signature avec la clé privée dérivée ou passée
  // Importation correcte de la clé privée pour la signature :
  const dpopSignedJwt = await new jose.SignJWT(payload)
    .setProtectedHeader(dpopHeader)
    .setIssuedAt()
    .setExpirationTime('2m')
    .sign(privateKey);

  // 5. Calculer l'empreinte digitale de la clé publique
  const thumbprint = await jose.calculateJwkThumbprint(publicKeyJWK, 'sha256');

  // 6. Effectuer la requête HTTP
  const response = await axios.get(url, {
    headers: {
      'Authorization': 'Bearer ACCESS_TOKEN',
      'DPoP': dpopSignedJwt,
      'DPOP': thumbprint
    }
  });

  return response.data;
}

Exigences de validation côté serveur

Implémenter le client n'est que la moitié du travail. Le serveur de ressources doit également être configuré pour valider les preuves DPoP. Si le serveur reçoit une demande avec un en-tête DPOP, il doit effectuer les vérifications suivantes :

  1. Vérification de la signature : Décodez le JWT dans l'en-tête DPoP et vérifiez la signature à l'aide de la clé publique fournie dans la revendication jwk.
  2. Validation de l'horodatage : Assurez-vous que la revendication iat (émis à) n'est pas trop ancienne (généralement dans les 60 secondes) pour empêcher les attaques par rejeu.
  3. Correspondance d'URL : Vérifiez que la revendication aud correspond à l'URL du serveur de ressources.
  4. Liaison de jeton : Liez le jeton d'accès à la clé publique. Si le jeton d'accès a été émis avec un paramètre de liaison, le serveur doit s'assurer que la clé publique dans la preuve DPoP correspond à la liaison attendue.

La plupart des fournisseurs d'identité modernes comme Auth0, Keycloak et Microsoft Identity Web prennent désormais en charge DPoP par défaut. Pour les implémentations personnalisées, des bibliothèques telles que passport-oauth2-provider ou Owin.Security.Providers ont ajouté un support expérimental ou stable pour la validation DPoP.

Conclusion

L'implémentation de la liaison de jetons OAuth2 via DPoP augmente considérablement la barre pour les attaquants. En garantissant que les jetons volés sont inutiles sans la clé privée correspondante, vous atténuez le risque de rejeu et de vol de jetons. Bien que la complexité de l'implémentation augmente légèrement des deux côtés (client et serveur), les avantages en matière de sécurité pour les APIs à haute valeur sont substantiels. À mesure que l'écosystème web évolue vers des architectures de confiance zéro, l'adoption de DPoP n'est plus seulement une bonne pratique, elle devient une nécessité.

Share: