Passer au contenu principal
Demonstrating Proof-of-Possession (DPoP) est une extension du framework OAuth 2.0 qui lie, ou restreint à l’expéditeur, les à l’aide de la cryptographie asymétrique et des (JWT) au niveau de la couche applicative. DPoP garantit que seule l’application cliente qui a demandé le jeton d’accès, et qui possède la clé privée, peut l’utiliser. Cela empêche l’utilisation abusive de jetons volés. DPoP utilise une paire de clés publique/privée pour créer une preuve DPoP sous la forme d’un JSON Web Token (JWT) signé. La preuve DPoP contient :
  • La clé publique de l’application cliente (jwk).
  • Le payload qui fait référence à la requête de jeton d’accès, y compris la méthode (htm) et l’URI (htu).
  • Une signature créée à l’aide de la clé privée de l’application cliente.
  • Un identifiant unique (jti) pour prévenir les attaques par rejeu.
  • Pour chaque requête d’API, un hachage SHA-256 (ath) du jeton d’accès encodé en base64url.
  • Facultatif : pour les , une revendication nonce pour s’assurer que l’application cliente a généré récemment le JWT de preuve DPoP.
L’application cliente envoie le JWT de preuve DPoP dans une requête de jeton d’accès au Auth0. Après avoir validé le JWT de preuve DPoP, le serveur d’autorisation Auth0 lie le jeton d’accès émis à la clé publique de l’application cliente.

Cas d’utilisation courants

Découvrez quelques cas d’utilisation courants de DPoP :
  • Applications monopages (SPA) et applications mobiles : En tant qu’applications publiques, les SPA et les applications mobiles ne disposent pas d’un environnement sûr et confidentiel, comme un serveur backend, pour stocker en toute sécurité les , ce qui les rend vulnérables au vol de jetons. DPoP remédie à cette vulnérabilité en liant les jetons d’accès à la clé publique de l’application cliente, afin de créer un JWT de preuve DPoP. L’application cliente signe le JWT de preuve DPoP avec sa clé privée et l’inclut dans une demande d’autorisation. Le serveur d’autorisation Auth0 valide le JWT de preuve DPoP et, s’il est valide, lie le jeton d’accès émis à la clé publique de l’application cliente.
  • Intégrations à des API tierces : Si un agent d’IA intégré à votre application cliente appelle une API tierce au nom de l’utilisateur à l’aide d’un JWT de preuve DPoP, le peut valider cryptographiquement que la demande provient de l’agent d’IA et non d’un tiers non autorisé.

Types d’octroi d’application pris en charge

Auth0 prend en charge les types d’octroi d’application suivants pour le mécanisme de sender constraining avec DPoP :
Type d’octroiDescription
authorization_codeOctroi par code d’autorisation
client_credentialsOctroi par identifiants de l’application
passwordOctroi par mot de passe du propriétaire de la ressource
refresh_tokenOctroi par jeton d’actualisation
urn:ietf:params:oauth:grant-type:device_codeOctroi d’autorisation d’appareil
http://auth0.com/oauth/grant-type/password-realmUtiliser un octroi d’extension semblable à l’octroi par mot de passe du propriétaire de la ressource, avec la possibilité d’indiquer un realm précis
http://auth0.com/oauth/grant-type/passwordless/otpDemande d’octroi Passwordless
http://auth0.com/oauth/grant-type/mfa-oobDemande d’octroi d’authentification multifacteur hors bande (OOB)
http://auth0.com/oauth/grant-type/mfa-otpDemande d’octroi d’authentification multifacteur OTP
http://auth0.com/oauth/grant-type/mfa-recovery-codeDemande d’octroi d’authentification multifacteur par code de récupération
urn:ietf:params:oauth:grant-type:token-exchangeDemande d’octroi Custom Token Exchange
urn:okta:params:oauth:grant-type:webauthnDemande d’octroi WebAuthn

Fonctionnement

Le diagramme de séquence suivant illustre les grandes étapes du flux DPoP d’Auth0 :
  1. Lorsqu’elle demande un jeton d’accès au serveur d’autorisation d’Auth0, l’application cliente génère une paire de clés cryptographiques unique et utilise la clé publique pour prouver qu’elle possède la clé privée.
  2. L’application cliente génère le JWT de preuve DPoP et l’envoie au point de terminaison /token du serveur d’autorisation d’Auth0.
  3. Le serveur d’autorisation d’Auth0 vérifie le JWT de preuve DPoP et, s’il est valide, émet le jeton d’accès et le lie à la clé publique de l’application cliente.
  4. Avant d’appeler la Customer API, l’application cliente génère un nouveau JWT de preuve DPoP pour prouver qu’elle possède la clé privée associée au jeton. L’application cliente envoie le JWT de preuve DPoP et le jeton d’accès lié à l’émetteur au serveur de ressources.
  5. Le serveur de ressources vérifie le JWT de preuve DPoP, garantissant que seul le détenteur légitime du jeton, c’est-à-dire l’application cliente d’origine, peut l’utiliser pour accéder aux ressources protégées. Pour obtenir un jeton d’accès à partir d’un jeton d’actualisation, l’application cliente génère un nouveau JWT de preuve DPoP, de sorte que le jeton d’actualisation soit lié à la clé publique de l’application cliente.

Lier des jetons à l’expéditeur à l’aide de DPoP dans Auth0

Le schéma suivant illustre le flux de bout en bout pour lier des jetons à l’expéditeur à l’aide de DPoP dans Auth0 :
Les sections suivantes vous guident étape par étape dans le flux DPoP d’Auth0, avec des exemples de code pour l’implémenter :

Prérequis

Avant de commencer, assurez-vous d’effectuer ce qui suit :

Étape 1 : L’application cliente génère une paire de clés DPoP

Pour DPoP, l’application cliente doit générer une paire de clés cryptographiques asymétriques. Auth0 prend en charge l’utilisation de courbes elliptiques, notamment pour les clés ES256. Cette paire de clés est propre à votre application cliente et doit être stockée de manière sécurisée, par exemple dans un magasin de clés sécurisé par le matériel. L’application cliente conserve la clé privée secrète, tout en incluant la clé publique dans le JSON Web Token (JWT) de preuve DPoP, qui sert de « preuve de possession » à l’étape 2.

Étape 2 : L’application cliente crée un JWT de preuve DPoP

Avant de demander un jeton d’accès lié à DPoP au point de terminaison /token du serveur d’autorisation Auth0, votre application cliente doit créer un JWT de preuve DPoP. Un JWT de preuve DPoP est un JSON Web Token (JWT) signé avec la clé privée de votre application cliente, qui sert de « preuve de possession ». Le JWT de preuve DPoP se compose d’un en-tête JWT et d’un payload contenant des claims liés à la demande de jeton :

Claims de l’en-tête du JWT

Claim du JWT de preuve DPoPDescription
typValeur définie sur dpop+jwt.
algL’algorithme de signature asymétrique utilisé, par exemple RS256 ou ES256.
jwkUne représentation JWK (JSON Web Key) de la clé publique de votre application.

Claim du payload JWT

Claim du JWT de preuve DPoPDescription
jtiUn identifiant unique du JWT pour prévenir les attaques par rejeu.
htmLa méthode HTTP de la requête à laquelle correspond la preuve DPoP, par exemple POST pour les demandes de jeton et GET pour les appels d’API.
htuL’URI HTTP de la requête à laquelle correspond le JWT de preuve DPoP, sans le fragment ni les paramètres de requête. Par exemple : https://api.example.com/data?param=1#section1 devient https://api.example.com/data.
iatL’horodatage de création du JWT.
athPour les appels d’API avec un jeton d’accès, un hachage SHA-256 du jeton d’accès encodé en base64url.
noncePour les applications publiques qui exigent un nonce, une valeur nonce fournie par le serveur.
Une fois que l’application cliente a créé le JWT de preuve DPoP, elle le signe avec la clé privée générée à l’étape 1. L’exemple de code suivant montre comment créer et signer un JWT de preuve DPoP dans votre application cliente :
import { generateKeyPairSync, randomBytes } from 'node:crypto';
import jwt from 'jsonwebtoken';

// Générer une paire de clés DPoP
const keyPair = generateKeyPairSync('ec', {
  namedCurve: 'P-256',
});

// Construire le JWT de preuve DPoP pour la demande de jeton
const jti = randomBytes(16).toString('base64url');
const jwk = keyPair.publicKey.export({ format: 'jwk' });
const dpopHeader = jwt.sign({
    jti,
    htm: 'POST',
    htu: 'https://[TENANT]/oauth/token',
    iat: Date.now() / 1000,
  },
  keyPair.privateKey,
  {
    algorithm: 'ES256',
    header: {
      typ: 'dpop+jwt',
      jwk,
    },
  });

Étape 3 : L’application cliente demande un jeton associé à DPoP

Lorsque votre application cliente demande un jeton d’accès au point de terminaison /token du serveur d’autorisation Auth0, elle inclut le JWT de preuve DPoP dans l’en-tête HTTP de la requête :
DPoP: {DPoP_proof_JWT_value}
Voici un exemple de requête de jeton d’accès qui inclut l’en-tête HTTP DPoP contenant un JWT de preuve DPoP :
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
DPoP: {DPoP Proof JWT}
Authorization: Basic Y2xpZW50MTIzOm15c2VjcmV0
Cache-Control: no-cache
grant_type=client_credentials&client_id=client123
Pour implémenter la demande d’un jeton d’accès lié à DPoP dans votre application cliente, utilisez l’exemple de code suivant, qui effectue les opérations suivantes :
  1. Renseigne l’en-tête HTTP DPoP avec une preuve JWT DPoP signée.
  2. Envoie l’en-tête HTTP DPoP avec une preuve JWT DPoP signée dans une requête de jeton d’accès vers le point de terminaison /token.
  3. Traite la réponse du serveur d’autorisation Auth0.
// Effectuer la requête vers le point de terminaison /oauth/token
// Remplacer [...] par votre grant_type, client_id et URL de locataire réels
const response = await fetch('https://[TENANT]/oauth/token', {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: '...',
      client_id: '...',
      // Autres paramètres du corps ici
    }),
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      // Ajouter l'en-tête DPoP
      dpop: dpopHeader
    }
  });

// Traiter la réponse du serveur d'autorisation Auth0
const result = await response.json();
console.log('Initial token request result:', result);

Applications publiques

Si une application publique, comme une application monopage (SPA) ou une application mobile, demande un jeton d’accès lié à DPoP, vous n’aurez pas de Secret client ni d’autres paramètres d’authentification de l’application. Dans ce cas, et conformément à la RFC 9449, Auth0 exige que votre en-tête HTTP DPoP contienne une valeur de afin de s’assurer que l’application cliente a bien généré récemment le JWT de preuve DPoP. Il s’agit du comportement attendu, car cela permet au serveur d’autorisation de vérifier que la preuve DPoP est récente et de limiter la fenêtre pendant laquelle elle peut être utilisée. Si une application publique envoie une requête /token sans inclure de valeur nonce dans l’en-tête HTTP DPoP, Auth0 répond avec un code HTTP 400 et un message d’erreur comme le suivant :
{
  error: 'use_dpop_nonce',
  error_description: 'Le serveur d'autorisation exige un nonce dans la preuve DPoP'
}
Auth0 inclut un en-tête DPoP-Nonce dans les en-têtes de réponse. Cela suit le flux standard de « défi-réponse » défini dans la spécification DPoP. Vous devez utiliser la valeur de l’en-tête DPoP-Nonce et régénérer la preuve DPoP (comme à l’étape 2), inclure une claim nonce ayant cette valeur, puis renvoyer la requête au point de terminaison /token. L’exemple de code suivant montre le flux de bout en bout lors de l’envoi, puis de la nouvelle tentative, d’une requête /token avec une claim nonce à partir d’une application publique :
import { generateKeyPairSync, randomBytes } from 'node:crypto';
import jwt from 'jsonwebtoken';

// Générer une paire de clés DPoP
const keyPair = generateKeyPairSync('ec', {
  namedCurve: 'P-256',
});

/**
 * Fonction utilitaire pour générer un JWT de preuve DPoP.
 * @param {string} method - Méthode HTTP (p. ex., 'POST', 'GET').
 * @param {string} url - URL complète de la requête.
 * @param {string} [nonce] - Valeur DPoP-Nonce optionnelle provenant du serveur.
 * @param {string} [accessToken] - Jeton d'accès optionnel à hacher pour la revendication 'ath'.
 * @returns {string} Le JWT de preuve DPoP signé.
 */
function generateDPoPHeader(method, url, nonce) {
  const jti = randomBytes(16).toString('base64url');
  const jwk = keyPair.publicKey.export({ format: 'jwk' });
  return jwt.sign({
      jti,
      htm: method,
      htu: url,
      iat: Date.now() / 1000,
      nonce
    },
    keyPair.privateKey,
    {
      algorithm: 'ES256',
      header: {
        typ: 'dpop+jwt',
        jwk,
      },
    });
  }

// Demander le jeton d'accès pour la première fois sans nonce 
async function getTokens(nonce) {
  const response = await fetch('https://[TENANT]/oauth/token', {
      method: 'POST',
      body: new URLSearchParams({
        grant_type: '...',
        client_id: '...',
        // Autres paramètres du corps ici
      }),
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        dpop: generateDPoPHeader('POST', 'https://[TENANT]/oauth/token', nonce),
      }
    });

  const result = await response.json();
  return { response, result };
}

// La première fois qu'on demande des jetons, on n'aura pas de nonce
let { response, result } = await getTokens(); 
console.log('Initial token request result:', result);

if (response.status === 400 && result.error === 'use_dpop_nonce') {
  const nonce = response.headers.get('dpop-nonce');

  console.log('Received nonce:', nonce);

  // Réessayer avec le nonce
  ({ response, result } = await getTokens(nonce)); 

  console.log('Tokens received:', result);
}

Étape 4 : Le serveur d’autorisation Auth0 valide le JWT de preuve DPoP

Lorsque le serveur d’autorisation Auth0 reçoit la demande de jeton, il effectue les opérations suivantes :
  • Extrait le JWT de preuve DPoP, sa clé publique et sa signature.
  • Vérifie la signature à l’aide de la clé publique fournie.
  • Valide les claims htm, htu, jti, et iat.
  • Si tout est valide, il émet un jeton d’accès. Le serveur d’autorisation Auth0 inclut une claim de confirmation, cnf, dans le jeton d’accès. La claim cnf contient l’empreinte numérique (hachage) de la clé publique extraite du JWT de preuve DPoP. En l’incluant dans le jeton d’accès, le serveur d’autorisation Auth0 lie le jeton d’accès à cette clé publique précise, autrement dit, le lie à l’émetteur.
  • Définit le token_type dans l’en-tête Authorization à DPoP plutôt qu’à Bearer dans la réponse de jeton. Traditionnellement, lorsque le jeton d’accès est transmis dans l’en-tête Authorization, il est défini à Bearer. Toutefois, comme nous transmettons un jeton d’accès lié à une clé publique au moyen de DPoP, il est plutôt défini à DPoP.
  • Le serveur d’autorisation Auth0 émet ensuite le jeton d’accès DPoP lié à l’émetteur à votre application.

Étape 5 : L’application cliente appelle l’API avec le jeton lié à DPoP et le JWT de preuve DPoP

Pour chaque appel d’API à un serveur de ressources qui applique DPoP, votre application cliente doit présenter à la fois le jeton d’accès lié à DPoP et un nouveau JWT de preuve DPoP. En exigeant un JWT de preuve DPoP pour chaque requête d’API, DPoP garantit que seule l’application cliente qui possède la clé privée peut utiliser le jeton d’accès. Pour une nouvelle requête d’API, l’application cliente :
  1. Génère un nouveau JWT de preuve DPoP avec les claims suivantes :
  • La claim htm correspond à la méthode HTTP de la requête d’API, par exemple GET ou POST.
  • La claim htu correspond à l’URI de la requête d’API.
  • La claim ath correspond au hachage SHA-256 encodé en base64url du jeton d’accès lié à DPoP que vous avez reçu à l’étape 3.
  1. Signe le nouveau JWT de preuve DPoP de façon cryptographique avec la clé privée de l’application.
  2. Inclut le jeton d’accès lié à DPoP dans l’en-tête Authorization à l’aide du schéma d’authentification DPoP :
// Le schéma DPoP correspond au token_type reçu du serveur d'autorisation
Authorization: DPoP {access_token}
  1. Incluez le JWT de preuve DPoP qui vient d’être généré dans l’en-tête HTTP DPoP :
DPoP: {new_dpop_proof_jwt}
L’en-tête HTTP DPoP doit inclure une claim ath supplémentaire. La claim ath est un hachage SHA256 du jeton d’accès émis, encodé en base64url. Le serveur de ressources :
  • Reçoit la requête à l’API et extrait le jeton d’accès, la preuve JWT DPoP, la clé publique et la signature.
  • Vérifie la signature de la preuve JWT DPoP à l’aide de la clé publique provenant de son en-tête jwk.
  • Valide les claims htm, htu, jti, iat et ath.
  • Vérifie que la clé publique indiquée dans la preuve JWT DPoP par son en-tête jwk correspond à la clé publique liée au jeton d’accès par la claim cnf.jkt dans le jeton d’accès.
Si toutes les vérifications réussissent, le serveur de ressources autorise la requête. Sinon, il la rejette et l’accès est refusé. L’exemple de code suivant demande un jeton d’accès à Auth0 à l’aide de DPoP, puis appelle le point de terminaison /userinfo à l’aide d’un jeton d’accès lié à DPoP :
import { generateKeyPairSync, randomBytes, createHash } from 'node:crypto';
import jwt from 'jsonwebtoken';

const keyPair = generateKeyPairSync('ec', {
  namedCurve: 'P-256',
});

function hashToken(token) {
  return createHash('sha256').update(token).digest('base64url');
}

function generateDPoPHeader(method, url, nonce, accessToken) {
  const jti = randomBytes(16).toString('base64url');
  const jwk = keyPair.publicKey.export({ format: 'jwk' });
  return jwt.sign({
      jti,
      htm: method,
      htu: url,
      iat: Date.now() / 1000,
      nonce,

      // Inclure facultativement une revendication `ath` contenant un hachage du jeton d'accès
      ...(accessToken ? { ath: hashToken(accessToken) } : {}),
    },
    keyPair.privateKey,
    {
      algorithm: 'ES256',
      header: {
        typ: 'dpop+jwt',
        jwk,
      },
    });
  }

async function getTokens(nonce) {
  const response = await fetch('https://[TENANT]/oauth/token', {
      method: 'POST',
      body: new URLSearchParams({
        grant_type: '...',
        client_id: '...',
        // Autres paramètres du corps ici
      }),
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        dpop: generateDPoPHeader('POST', 'https://test1.local.dev.auth0.com/oauth/token', nonce),
      }
    });

  const result = await response.json();
  return { response, result };
}

// La première fois, nous n'aurons pas de nonce
let { response, result } = await getTokens(); 
console.log('Initial token request result:', result);

if (response.status === 400 && result.error === 'use_dpop_nonce') {
  const nonce = response.headers.get('dpop-nonce');
  console.log('Received nonce:', nonce);
  ({ response, result } = await getTokens(nonce)); // Réessayer avec le nonce
  console.log('Tokens received:', result);
}

// Appeler maintenant /userinfo avec DPoP
const userInfoResponse = await fetch('https://[TENANT]/userinfo', {
  method: 'GET',
  headers: {
    // Transmettre notre jeton d'accès avec le schéma d'autorisation DPoP
    Authorization: `DPoP ${result.access_token}`,

    // Inclure un en-tête DPoP, cette fois avec le hachage du jeton d'accès
    dpop: generateDPoPHeader('GET', 'https://[TENANT]/userinfo', nonce, result.access_token),
  },
});

console.log('User info response status:', userInfoResponse.status);
console.log('User info result:', await userInfoResponse.json());

Étape 6 : Gérer le renouvellement des jetons avec DPoP

Lorsque votre jeton d’accès associé à DPoP expire, vous pouvez utiliser un pour en obtenir un nouveau. Une demande de jeton d’actualisation nécessite une preuve JWT DPoP générée à l’aide de la même paire de clés que celle utilisée pour la demande de jeton initiale. Voici le flux de jeton d’actualisation avec DPoP dans Auth0 : L’application cliente :
  • Envoie une demande de jeton d’actualisation au point de terminaison /token du serveur d’autorisation Auth0.
  • Génère une preuve JWT DPoP pour la demande de jeton d’actualisation (comme à l’étape 2, avec htm défini sur POST et htu défini sur l’URI du ).
  • Inclut la preuve JWT DPoP dans l’en-tête HTTP DPoP.
Le serveur d’autorisation Auth0 :
  • Valide la preuve JWT DPoP (comme à l’étape 4) et émet un nouveau jeton d’accès associé à DPoP.

Considérations importantes

Lorsque vous implémentez DPoP dans vos applications clientes, tenez compte des éléments suivants :
  • Sécurité de la clé privée : La sécurité de votre implémentation DPoP dépend de la sécurité de la clé privée de votre application. Vous devez donc la protéger contre tout accès non autorisé. Les clés privées devraient être générées et stockées dans un support matériel, et marquées comme non exportables.
  • Protection contre le rejeu (jti** et dpop-nonce):** La claim jti dans le JWT de preuve DPoP aide à prévenir les attaques par rejeu visant des ressources protégées, comme le point de terminaison /userinfo. Le serveur d’autorisation Auth0 ne vérifie actuellement pas la réutilisation de jti sur le point de terminaison /userinfo. Le serveur d’autorisation Auth0 renvoie un en-tête HTTP DPoP-Nonce dans sa réponse, que les applications publiques doivent inclure sous forme de claim nonce dans les JWT de preuve DPoP subséquents afin de renforcer la protection contre le rejeu.
  • Limites de débit : Comme le flux défi-réponse DPoP peut parfois nécessiter une requête initiale suivie d’une nouvelle tentative avec le nonce fourni par le serveur, chaque échange compte effectivement comme deux requêtes dans vos limites de débit du locataire Auth0. Assurez-vous que le volume de requêtes de votre application tient compte de cette surcharge.
  • Gestion des erreurs : Vous devez implémenter la logique nécessaire pour gérer les erreurs propres à DPoP renvoyées par le serveur d’autorisation Auth0 ou le serveur de ressources, comme invalid_dpop_proof ou use_dpop_nonce.
  • Types d’application : Utilisez DPoP pour les applications publiques, comme les applications monopages (SPA) ou les applications mobiles qui ne peuvent pas stocker un Secret client de manière sécuritaire. Pour les , comme les services backend avec des secrets client, DPoP ajoute une couche de sécurité, mais elles disposent déjà d’autres mécanismes de liaison à l’expéditeur.
  • Performance : Comme la génération et la signature d’un JWT de preuve DPoP pour chaque appel d’API ajoutent une légère surcharge, assurez-vous que les opérations cryptographiques de votre application sont efficaces.
  • Rotation des clés : Implémentez une stratégie de rotation de vos paires de clés DPoP pour renforcer la sécurité. Assurez-vous d’utiliser la même paire de clés pour une même session.
  • Persistance : Pour les applications qui doivent maintenir une session et réutiliser des jetons d’accès liés à DPoP, comme les SPA de longue durée, stockez et récupérez de façon sécuritaire la paire de clés originale lors des rechargements de l’application. Si une nouvelle paire de clés est générée ou si une autre paire de clés est utilisée, le jeton d’accès lié à DPoP devient invalide, puisqu’il est lié cryptographiquement à la clé publique de la paire originale. Vous pouvez stocker la paire de clés, par exemple, dans IndexedDB d’un navigateur ou dans le stockage sécurisé d’une application mobile.

Pour en savoir plus