Passer au contenu principal
L’application d’une contrainte d’émetteur aux jetons à l’aide de Demonstrating Proof-of-Possession (DPoP) est actuellement en accès anticipé. Pour demander l’accès à cette fonctionnalité, communiquez avec votre représentant Auth0.
Demonstrating Proof-of-Possession (DPoP) est une extension du framework OAuth 2.0 qui associe ou contraint par émetteur les au moyen de la cryptographie asymétrique et de (JWT) à 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 (DPoP Proof) sous forme de JSON Web Token (JWT) signé. La preuve DPoP contient :
  • La clé publique (jwk) du client.
  • La charge utile se rapportant à la requête de jeton d’accès, incluant la méthode (htm) et l’URI (htu).
  • Une signature créée à l’aide de la clé privée du client.
  • Un id unique (jti) pour prévenir les attaques par rejeu.
  • Pour chaque requête à une API, un hachage SHA-256 encodé en base64url (ath) du jeton d’accès.
  • Facultatif : pour les , une revendication nonce pour garantir 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. Une fois que le serveur d’autorisation Auth0 valide le JWT de preuve DPoP, il associe le jeton d’accès émis à la clé publique du client.

Cas d’utilisation courants

Découvrez quelques cas d’utilisation courants de DPoP :
  • Applications monopages (SPA) et applications mobiles : En tant que clients publics, les SPA et les applications mobiles n’ont pas d’environnement fiable et confidentiel, comme un serveur backend, pour stocker de façon sécuritaire les , ce qui les rend vulnérables au vol de jetons. DPoP atténue cette vulnérabilité de sécurité en liant les jetons d’accès à la clé publique de l’application cliente, ce qui crée un JWT de preuve DPoP. L’application cliente signe le JWT de preuve DPoP avec sa clé privée et l’envoie dans une requête 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 du client.
  • Intégrations d’API tierces : Si un agent d’IA intégré à votre application cliente appelle une API tierce pour le compte de l’utilisateur en utilisant un JWT de preuve DPoP, alors le peut vérifier cryptographiquement que la requête provient de l’agent d’IA et non d’un tiers non autorisé.

Types d’octroi d’autorisation d’application pris en charge

Auth0 prend en charge les types d’octroi d’autorisation d’application suivants pour la restriction de l’émetteur avec DPoP :
Type d’octroiDescription
authorization_codeOctroi par code d’autorisation
client_credentialsOctroi par identifiants de Client
passwordOctroi par mot de passe du propriétaire des ressources
refresh_tokenOctroi par Jeton d’actualisation
urn:ietf:params:oauth:grant-type:device_codeOctroi d’autorisation de dispositif
http://auth0.com/oauth/grant-type/password-realmUtiliser un octroi d’extension semblable à l’octroi par mot de passe du propriétaire des ressources, qui permet d’indiquer un realm spécifique
http://auth0.com/oauth/grant-type/passwordless/otpRequête d’octroi pour l’authentification sans mot de passe
http://auth0.com/oauth/grant-type/mfa-oobRequête d’octroi d’authentification multifacteur (AMF (MFA)) hors bande (OOB)
http://auth0.com/oauth/grant-type/mfa-otpRequête d’octroi d’authentification multifacteur (AMF (MFA)) par OTP
http://auth0.com/oauth/grant-type/mfa-recovery-codeRequête d’octroi d’authentification multifacteur (AMF (MFA)) par code de récupération
urn:ietf:params:oauth:grant-type:token-exchangeRequête d’octroi d’échange de jeton personnalisé
urn:okta:params:oauth:grant-type:webauthnRequête d’octroi WebAuthn

Fonctionnement

Le diagramme de séquence suivant illustre les grandes étapes du flux DPoP d’Auth0 :
  1. Lors de la demande d’un jeton d’accès auprès du Serveur d’autorisation 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 DPoP Proof JWT et l’envoie au point de terminaison /token du Serveur d’autorisation Auth0.
  3. Le Serveur d’autorisation Auth0 vérifie le DPoP Proof JWT et, s’il est valide, émet le jeton d’accès et le lie à la clé publique du client.
  4. Avant d’appeler l’API Customer, l’application cliente génère un nouveau DPoP Proof JWT pour prouver qu’elle possède la clé privée associée au jeton. L’application cliente envoie le DPoP Proof JWT et le jeton d’accès restreint à l’émetteur au serveur de ressources.
  5. Le serveur de ressources vérifie le DPoP Proof JWT afin de s’assurer que seul le propriétaire légitime du jeton, ou 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 DPoP Proof JWT, ce qui garantit que le jeton d’actualisation est lié à la clé publique du client.

Restreindre les jetons à l’expéditeur à l’aide de DPoP dans Auth0

Le diagramme suivant illustre le flux de bout en bout pour restreindre les jetons à l’expéditeur à l’aide de DPoP dans Auth0 :
Les sections suivantes vous guident étape par étape dans le flux DPoP dans Auth0 avec des exemples de code pour l’implémentation :

Prérequis

Avant de commencer, assurez-vous de :

Step 1: Client application generates a DPoP key pair

For DPoP, the client application must generate an asymmetric cryptographic key pair. Auth0 supports the use of Elliptic Curve, such as in ES256 keys. This key pair is unique to your client application and should be securely stored, for example, in a hardware-backed keystore. The client application keeps the private key secret while including the public key in the DPoP Proof JSON Web Token (JWT) that serves as the “proof of possession” in Step 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 client qui sert de « preuve de possession ». Le JWT de preuve DPoP se compose d’un en-tête JWT et d’une charge utile JWT qui contiennent des revendications liées à la requête de jeton :

Revendications d’en-tête JWT

Revendication JWT de preuve DPoPDescription
typDoit être défini sur dpop+jwt.
algL’algorithme de signature asymétrique utilisé, comme RS256 ou ES256.
jwkUne représentation JSON Web Key (JWK) de la clé publique de votre client.

Déclarations (claims) du payload JWT

Déclaration JWT de preuve DPoPDescription
jtiIdentifiant unique du JWT pour empêcher les attaques par rejeu.
htmMéthode HTTP de la requête pour laquelle la preuve DPoP est utilisée, par exemple POST pour les requêtes de jeton et GET pour les appels d’API.
htuURI HTTP de la requête pour laquelle le JWT de preuve DPoP est utilisé, 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.
iatHorodatage de création du JWT.
athPour les appels d’API avec un jeton d’accès, un hachage SHA-256 encodé en base64url du jeton d’accès.
noncePour les clients publics nécessitant un nonce, une valeur de 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 requête 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,
    },
  });

Step 3: Client application requests a DPoP-bound token

When your client application requests an access token from the Auth0 Authorization Server’s /token endpoint, it includes the DPoP Proof JWT in the HTTP header of the request:
DPoP: {DPoP_proof_JWT_value}
The following is an example access token request that includes the DPoP HTTP header populated with a DPoP Proof JWT:
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
To implement how to request a DPoP-bound access token in your client application, use the following code sample, which does the following:
  1. Populates the DPoP HTTP header with a signed DPoP Proof JWT.
  2. Sends the DPoP HTTP header with a signed DPoP Proof JWT in an access token request to the /token endpoint.
  3. Processes the response from the Auth0 Authorization Server.
// Make the request to the /oauth/token endpoint
// Replace [...] with your actual grant_type, client_id, and tenant URL
const response = await fetch('https://[TENANT]/oauth/token', {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: '...',
      client_id: '...',
      // Other body parameters here
    }),
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      // Add the DPoP header
      dpop: dpopHeader
    }
  });

// Process the response from the Auth0 Authorization Server
const result = await response.json();
console.log('Initial token request result:', result);

Clients publics

Si un client public, 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 client. Dans ce cas, Auth0 exige que votre en-tête HTTP DPoP contienne une valeur de afin de s’assurer que l’application cliente a généré récemment le JWT de preuve DPoP. Si un client public effectue une requête /token et n’inclut pas de valeur nonce dans l’en-tête HTTP DPoP, Auth0 répond avec un code HTTP 400 et un message d’erreur semblable au 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. Vous devez utiliser la valeur de l’en-tête DPoP-Nonce, régénérer la preuve DPoP (comme à l’étape 2), inclure une revendication nonce avec cette valeur, puis soumettre de nouveau 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’exécution d’une requête /token avec une revendication nonce à partir d’un client public :
import { generateKeyPairSync, randomBytes } from 'node:crypto';
import jwt from 'jsonwebtoken';

// Generate a DPoP Key Pair
const keyPair = generateKeyPairSync('ec', {
  namedCurve: 'P-256',
});

/**
 * Fonction auxiliaire 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,
      },
    });
  }

// Request access token the first time without nonce 
async function getTokens(nonce) {
  const response = await fetch('https://[TENANT]/oauth/token', {
      method: 'POST',
      body: new URLSearchParams({
        grant_type: '...',
        client_id: '...',
        // Other body parameters here
      }),
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        dpop: generateDPoPHeader('POST', 'https://[TENANT]/oauth/token', nonce),
      }
    });

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

// The first time we request tokens, we won't have a 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);

  // Retry with the nonce
  ({ response, result } = await getTokens(nonce)); 

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

Step 4: Auth0 Authorization Server validates the DPoP Proof JWT

When the Auth0 Authorization Server receives the token request, it does the following:
  • Extracts the DPoP Proof JWT, its public key, and signature.
  • Verifies the signature using the provided public key.
  • Validates the htm, htu, jti, and iat claims.
  • If valid, it issues an access token. The Auth0 Authorization Server includes a confirmation claim, cnf, in the access token. The cnf claim contains the thumbprint (hash) of the public key taken from the DPoP Proof JWT. By including it in the access token, the Auth0 Authorization Server binds the access token to that specific public key, or “sender-constrains” the access token.
  • Sets the token_type in the Authorization header to DPoP instead of Bearer in the token response. Traditionally, when the access token is passed in the Authorization header, it is set to Bearer. However, because we’re passing an access token bound to a public key using DPoP, it is set to DPoP instead.
  • The Auth0 Authorization Server then issues the DPoP sender-constrained access token to your client application.

Step 5: Client application calls API with the DPoP-bound token and DPoP Proof JWT

For every API call to a resource server that enforces DPoP, your client application must present both the DPoP-bound access token and a new DPoP Proof JWT. By requiring a DPoP Proof JWT with every API request, DPoP ensures that only the client application that possesses the private key can use the access token. For a new API request, the client application:
  1. Generates a new DPoP Proof JWT with the following claims:
  • The htm claim is the API request’s HTTP method, such as GET or POST.
  • The htu claim is the API request’s URI.
  • The ath claim is the base64url-encoded SHA-256 hash of the DPoP-bound access token you received in Step 3.
  1. Cryptographically signs the new DPoP Proof JWT with the client’s private key.
  2. Includes the DPoP-bound access token in the Authorization header using the DPoP authentication scheme:
// The DPoP scheme aligns with the token_type received from the Authorization Server
Authorization: DPoP {access_token}
  1. Includes the newly generated DPoP Proof JWT in the DPoP HTTP header:
DPoP: {new_dpop_proof_jwt}
The DPoP HTTP header must include an additional ath claim. The ath claim is a base64url encoded SHA256 hash of the issued access token. The resource server:
  • Receives the API request and extracts the access token, DPoP JWT proof, public key, and signature.
  • Verifies the DPoP Proof JWT’s signature using the public key from its jwk header.
  • Validates the htm, htu, jti, iat, and ath claims.
  • Verifies that the public key indicated in the DPoP Proof JWT via its jwk header matches the public key bound to the access token via the cnf.jkt claim in the access token.
If all checks pass, the resource server authorizes the request. If not, it rejects the request, and access is denied. The following code sample requests an access token from Auth0 using DPoP and then calls the /userinfo endpoint using a DPoP-bound access token:
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,

      // Optionally include an `ath` claim containing an access token hash
      ...(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: '...',
        // Other body parameters here
      }),
      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 };
}

// The first time we do this, we won't have a 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)); // Retry with the nonce
  console.log('Tokens received:', result);
}

// Now call /userinfo with DPoP
const userInfoResponse = await fetch('https://[TENANT]/userinfo', {
  method: 'GET',
  headers: {
    // Pass our access token using the DPoP authorization scheme
    Authorization: `DPoP ${result.access_token}`,

    // Include a DPoP header, this time with access token hash
    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 l’actualisation des jetons avec DPoP

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

Important considerations

When implementing DPoP in your client applications, consider the following:
  • Private key security: The security of your DPoP implementation depends on the security of your client’s private key, so you must protect it from unauthorized access. Private keys should be generated and stored in a hardware-backed medium and marked as non-exportable.
  • Replay protection (jti** and dpop-nonce):** The jti claim in the DPoP Proof JWT helps prevent replay attacks for protected resources, such as the /userinfo endpoint. The Auth0 Authorization Server currently does not check jti reuse on the /userinfo endpoint. The Auth0 Authorization Server issues a DPoP-Nonce HTTP header in its response, which public clients must include as a nonce claim in subsequent DPoP Proof JWTs for enhanced replay protection.
  • Error handling: You are responsible for implementing logic to handle DPoP-specific errors from the Auth0 Authorization Server or resource server, such as invalid_dpop_proof or use_dpop_nonce.
  • Client types: Use DPoP for public clients, such as Single Page Applications (SPAs) or mobile apps that cannot securely store a client secret. For , such as backend services with client secrets, DPoP adds a layer of security, but they already have other sender-constraining mechanisms.
  • Performance: Because generating and signing DPoP Proof JWTs for every API call adds a small overhead, ensure your client application’s cryptographic operations are efficient.
  • Key rotation: Implement a strategy for rotating your DPoP key pairs for enhanced security. Make sure you use the same key pair for the same session.
  • Persistence: For client applications that need to maintain a session and reuse DPoP-bound access tokens, such as long-lived SPAs, securely persist and retrieve the original generated key pair across application reloads. If a new key pair is generated or a different key pair is used, the DPoP-bound access token becomes invalid, as it is cryptographically tied to the public key of the original pair. You can persist the key pair, for example, in a browser’s IndexedDB or a mobile app’s secure storage.

En savoir plus