Saltar al contenido principal
Demonstrating Proof-of-Possession (DPoP) es una extensión de OAuth 2.0 que vincula o restringe al remitente los mediante criptografía asimétrica y (JWT) en la capa de aplicación. DPoP garantiza que solo la aplicación cliente que solicitó el token de acceso y que posee la clave privada pueda usarlo. Esto evita el uso indebido de tokens robados. DPoP usa una clave pública/privada para crear una prueba DPoP como un JSON Web Token (JWT) firmado. La prueba DPoP contiene:
  • La clave pública del cliente (jwk).
  • La carga útil que hace referencia a la solicitud del token de acceso, incluido el método (htm) y el URI (htu).
  • Una firma creada con la clave privada del cliente.
  • Un ID único (jti) para evitar ataques de repetición.
  • Para cada solicitud a la API, un hash SHA-256 (ath) del token de acceso codificado en base64url.
  • Opcional: Para , un claim nonce para garantizar que la aplicación cliente haya generado recientemente el JWT de prueba de DPoP.
La aplicación cliente envía el JWT de prueba de DPoP en una solicitud de token de acceso al de Auth0. Después de que el Servidor de autorización de Auth0 valide el JWT de prueba de DPoP, vincula el token de acceso emitido a la clave pública del cliente.

Casos de uso comunes

Conozca algunos casos de uso comunes de DPoP:
  • Aplicaciones de página única (SPA) y aplicaciones móviles: Como clientes públicos, las SPA y las aplicaciones móviles carecen de un entorno confiable y confidencial, como el de un servidor backend, para almacenar de forma segura , lo que las hace vulnerables al robo de tokens. DPoP aborda esta vulnerabilidad de seguridad vinculando los tokens de acceso a la clave pública de la aplicación cliente y creando un JWT de prueba de DPoP. La aplicación cliente firma el JWT de prueba de DPoP con su clave privada y lo envía en una solicitud de autorización. El Servidor de autorización de Auth0 valida el JWT de prueba de DPoP y, si es válido, vincula el token de acceso emitido a la clave pública del cliente.
  • Integraciones con API de terceros: Si un agente de IA integrado con su aplicación cliente llama a una API de terceros en nombre del usuario mediante un JWT de prueba de DPoP, entonces el puede validar criptográficamente que la solicitud proviene del agente de IA y no de un tercero no autorizado.

Tipos de concesión de aplicación compatibles

Auth0 admite los siguientes tipos de concesión de aplicación para Sender Constraining con DPoP:
Tipo de concesiónDescripción
authorization_codeConcesión de código de autorización
client_credentialsConcesión de credenciales de cliente
passwordConcesión de contraseña del propietario del recurso
refresh_tokenConcesión de Token de actualización
urn:ietf:params:oauth:grant-type:device_codeConcesión de autorización de dispositivo
http://auth0.com/oauth/grant-type/password-realmUtiliza una concesión de extensión similar a la concesión de contraseña del propietario del recurso que incluye la capacidad de indicar un realm específico
http://auth0.com/oauth/grant-type/passwordless/otpSolicitud de concesión sin contraseña
http://auth0.com/oauth/grant-type/mfa-oobSolicitud de concesión OOB de Autenticación multifactor
http://auth0.com/oauth/grant-type/mfa-otpSolicitud de concesión OTP de Autenticación multifactor
http://auth0.com/oauth/grant-type/mfa-recovery-codeSolicitud de concesión de código de recuperación de Autenticación multifactor
urn:ietf:params:oauth:grant-type:token-exchangeSolicitud de concesión de Custom Token Exchange
urn:okta:params:oauth:grant-type:webauthnSolicitud de concesión de WebAuthn

Cómo funciona

El siguiente diagrama de secuencia ilustra los pasos generales del flujo DPoP de Auth0:
  1. Al solicitar un token de acceso al Servidor de autorización de Auth0, la aplicación cliente genera un par de claves criptográficas único y usa la clave pública para demostrar que posee la clave privada.
  2. La aplicación cliente genera el JWT de prueba de DPoP y lo envía al endpoint /token del Servidor de autorización de Auth0.
  3. El Servidor de autorización de Auth0 verifica el JWT de prueba de DPoP y, si es válido, emite el token de acceso y lo vincula a la clave pública del cliente.
  4. Antes de llamar a la API del cliente, la aplicación cliente genera un nuevo JWT de prueba de DPoP para demostrar que posee la clave privada asociada al token. La aplicación cliente envía el JWT de prueba de DPoP y el token de acceso restringido al remitente al servidor de recursos.
  5. El servidor de recursos verifica el JWT de prueba de DPoP, lo que garantiza que solo el propietario legítimo del token, o la aplicación cliente original, pueda usarlo para acceder correctamente a recursos protegidos. Para solicitar un token de acceso a partir de un token de actualización, la aplicación cliente genera un nuevo JWT de prueba de DPoP, lo que garantiza que el token de actualización quede vinculado a la clave pública del cliente.

Aplicar restricción del remitente a los tokens mediante DPoP en Auth0

El siguiente diagrama muestra el flujo de extremo a extremo para aplicar restricción del remitente a los tokens mediante DPoP en Auth0:
Las siguientes secciones le guían paso a paso por el flujo de DPoP en Auth0 con ejemplos de código para implementarlo:

Requisitos previos

Antes de comenzar, asegúrate de haber hecho lo siguiente:

Paso 1: La aplicación cliente genera un par de claves DPoP

Para DPoP, la aplicación cliente debe generar un par de claves criptográficas asimétricas. Auth0 admite el uso de curvas elípticas, como las claves ES256. Este par de claves es exclusivo de su aplicación cliente y debe almacenarse de forma segura, por ejemplo, en un almacén de claves respaldado por hardware. La aplicación cliente mantiene la clave privada en secreto e incluye la clave pública en el JSON Web Token (JWT) de prueba de DPoP, que actúa como la “prueba de posesión” en el Paso 2.

Paso 2: La aplicación cliente crea un JWT de prueba de DPoP

Antes de solicitar un token de acceso vinculado a DPoP en el endpoint /token del Servidor de autorización de Auth0, tu aplicación cliente debe crear un JWT de prueba de DPoP. Un JWT de prueba de DPoP es un JSON Web Token (JWT) firmado con la clave privada de tu cliente que actúa como la «prueba de posesión». El JWT de prueba de DPoP consta de un encabezado JWT y una carga útil que contiene claim vinculadas a la solicitud del token:

claim del encabezado del JWT

Claim de prueba JWT de DPoPDescripción
typSe establece en dpop+jwt.
algEl algoritmo de firma asimétrica utilizado, como RS256 o ES256.
jwkUna representación de JSON Web Key (JWK) de la clave pública de su cliente.

claim de la carga útil del JWT

Claim del JWT de prueba de DPoPDescripción
jtiUn identificador único del JWT para evitar ataques de repetición.
htmEl método HTTP de la solicitud para la que se genera la prueba DPoP, como POST para solicitudes de token y GET para llamadas a la API.
htuEl URI HTTP de la solicitud para la que se genera el JWT de prueba de DPoP, sin el fragmento ni los parámetros de consulta. Por ejemplo: https://api.example.com/data?param=1#section1 pasa a ser https://api.example.com/data.
iatLa marca de tiempo de creación del JWT.
athPara llamadas a la API con un token de acceso, un hash SHA-256 del token de acceso codificado en base64url.
noncePara clientes públicos que requieren un nonce, un valor nonce proporcionado por el servidor.
Una vez que la aplicación cliente crea el JWT de prueba de DPoP, lo firma con la clave privada generada en Paso 1. El siguiente ejemplo de código muestra cómo crear y firmar un JWT de prueba de DPoP en la aplicación cliente:
import { generateKeyPairSync, randomBytes } from 'node:crypto';
import jwt from 'jsonwebtoken';

// Generar un par de claves DPoP
const keyPair = generateKeyPairSync('ec', {
  namedCurve: 'P-256',
});

// Construir el JWT de prueba DPoP para la solicitud de token
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,
    },
  });

Paso 3: La aplicación cliente solicita un token vinculado a DPoP

Cuando la aplicación cliente solicita un token de acceso al endpoint /token del Servidor de autorización de Auth0, incluye la prueba JWT de DPoP en el encabezado HTTP de la solicitud:
DPoP: {DPoP_proof_JWT_value}
A continuación se muestra un ejemplo de solicitud de token de acceso que incluye el encabezado HTTP DPoP con un JWT de prueba de 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
Para implementar la solicitud de un token de acceso vinculado a DPoP en su aplicación cliente, use el siguiente ejemplo de código, que hace lo siguiente:
  1. Rellena el encabezado HTTP DPoP con un JWT de prueba de DPoP firmado.
  2. Envía el encabezado HTTP DPoP con un JWT de prueba de DPoP firmado en una solicitud de token de acceso al endpoint /token.
  3. Procesa la respuesta del Servidor de autorización de Auth0.
// Realiza la solicitud al endpoint /oauth/token
// Reemplaza [...] con tu grant_type, client_id y URL de tenant reales
const response = await fetch('https://[TENANT]/oauth/token', {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: '...',
      client_id: '...',
      // Otros parámetros del cuerpo aquí
    }),
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      // Agrega el encabezado DPoP
      dpop: dpopHeader
    }
  });

// Procesa la respuesta del Servidor de autorización de Auth0
const result = await response.json();
console.log('Initial token request result:', result);

Clientes públicos

Si un cliente público, como una aplicación de una sola página (SPA) o una aplicación móvil, solicita un token de acceso vinculado a DPoP, no tendrá un secreto del cliente ni otros parámetros de autenticación del cliente. En este caso, y de acuerdo con RFC 9449, Auth0 exige que el encabezado HTTP DPoP incluya un valor de para garantizar que la aplicación cliente haya generado recientemente el JWT de prueba de DPoP. Este es el comportamiento esperado, ya que permite al Servidor de autorización asegurarse de que la prueba de DPoP sea reciente y limita el período durante el que puede usarse. Si un cliente público realiza una solicitud a /token y no incluye un valor nonce en el encabezado HTTP DPoP, Auth0 responde con un código HTTP 400 y un mensaje de error como el siguiente:
{
  error: 'use_dpop_nonce',
  error_description: 'El servidor de autorización requiere nonce en la prueba DPoP'
}
Auth0 incluye el encabezado DPoP-Nonce en los encabezados de respuesta. Esto sigue el flujo estándar de “desafío-respuesta” definido en la especificación de DPoP. Debe usar el valor del encabezado DPoP-Nonce y volver a generar la prueba DPoP (como en el Paso 2), incluir un claim nonce con ese valor y volver a enviar la solicitud al endpoint /token. El siguiente ejemplo de código muestra el flujo completo al realizar y luego reintentar una solicitud a /token con un claim nonce desde un cliente público:
import { generateKeyPairSync, randomBytes } from 'node:crypto';
import jwt from 'jsonwebtoken';

// Generar un par de claves DPoP
const keyPair = generateKeyPairSync('ec', {
  namedCurve: 'P-256',
});

/**
 * Función auxiliar para generar un JWT de prueba de DPoP.
 * @param {string} method - Método HTTP (p. ej., 'POST', 'GET').
 * @param {string} url - URL completa de la solicitud.
 * @param {string} [nonce] - Valor DPoP-Nonce opcional del servidor.
 * @param {string} [accessToken] - Token de acceso opcional para calcular el hash del claim 'ath'.
 * @returns {string} El JWT de prueba de DPoP firmado.
 */
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,
      },
    });
  }

// Solicitar el token de acceso por primera vez sin nonce 
async function getTokens(nonce) {
  const response = await fetch('https://[TENANT]/oauth/token', {
      method: 'POST',
      body: new URLSearchParams({
        grant_type: '...',
        client_id: '...',
        // Otros parámetros del cuerpo aquí
      }),
      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 primera vez que solicitamos tokens no tendremos un 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);

  // Reintentar con el nonce
  ({ response, result } = await getTokens(nonce)); 

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

Paso 4: El Servidor de autorización de Auth0 valida el JWT de prueba de DPoP

Cuando el Servidor de autorización de Auth0 recibe la solicitud de token, hace lo siguiente:
  • Extrae el JWT de prueba de DPoP, su clave pública y la firma.
  • Verifica la firma con la clave pública proporcionada.
  • Valida los claim htm, htu, jti, e iat.
  • Si es válido, emite un token de acceso. El Servidor de autorización de Auth0 incluye un claim de confirmación, cnf, en el token de acceso. El claim cnf contiene la huella digital (hash) de la clave pública extraída del JWT de prueba de DPoP. Al incluirlo en el token de acceso, el Servidor de autorización de Auth0 vincula el token de acceso a esa clave pública concreta, es decir, restringe su uso al poseedor de esa clave.
  • Establece token_type en el encabezado Authorization como DPoP en lugar de Bearer en la respuesta del token. Tradicionalmente, cuando el token de acceso se envía en el encabezado Authorization, se establece como Bearer. Sin embargo, como aquí se envía un token de acceso vinculado a una clave pública mediante DPoP, se establece como DPoP.
  • A continuación, el Servidor de autorización de Auth0 emite el token de acceso restringido al remitente mediante DPoP para tu aplicación cliente.

Paso 5: La aplicación cliente llama a la API con el token vinculado a DPoP y el JWT de prueba de DPoP

Para cada llamada a la API a un servidor de recursos que aplique DPoP, su aplicación cliente debe presentar tanto el token de acceso vinculado a DPoP como un nuevo JWT de prueba de DPoP. Al exigir un JWT de prueba de DPoP con cada solicitud a la API, DPoP garantiza que solo la aplicación cliente que posee la clave privada pueda usar el token de acceso. Para una nueva solicitud a la API, la aplicación cliente:
  1. Genera un nuevo JWT de prueba de DPoP con los siguientes claim:
  • El claim htm es el método HTTP de la solicitud a la API, como GET o POST.
  • El claim htu es el URI de la solicitud a la API.
  • El claim ath es el hash SHA-256 codificado en base64url del token de acceso vinculado a DPoP que recibió en el Paso 3.
  1. Firma criptográficamente el nuevo JWT de prueba de DPoP con la clave privada del cliente.
  2. Incluye el token de acceso vinculado a DPoP en el encabezado Authorization mediante el esquema de autenticación DPoP:
// El esquema DPoP coincide con el token_type recibido del Servidor de autorización
Authorization: DPoP {access_token}
  1. Incluye el JWT de prueba de DPoP recién generado en el encabezado HTTP DPoP:
DPoP: {new_dpop_proof_jwt}
El encabezado HTTP DPoP debe incluir un claim ath adicional. El claim ath es un hash SHA256 del token de acceso emitido, codificado en base64url. El servidor de recursos:
  • Recibe la solicitud a la API y extrae el token de acceso, la prueba JWT de DPoP, la clave pública y la firma.
  • Verifica la firma de la prueba JWT de DPoP con la clave pública de su encabezado jwk.
  • Valida los claim htm, htu, jti, iat y ath.
  • Verifica que la clave pública indicada en la prueba JWT de DPoP a través de su encabezado jwk coincida con la clave pública asociada al token de acceso mediante el claim cnf.jkt del token de acceso.
Si todas las comprobaciones son correctas, el servidor de recursos autoriza la solicitud. De lo contrario, rechaza la solicitud y se deniega el acceso. El siguiente ejemplo de código solicita un token de acceso a Auth0 mediante DPoP y luego llama al endpoint /userinfo con un token de acceso vinculado a 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,

      // Opcionalmente, incluir un claim `ath` con el hash del token de acceso
      ...(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: '...',
        // Otros parámetros del cuerpo aquí
      }),
      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 primera vez que se ejecuta esto, no habrá un nonce disponible
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)); // Reintentar con el nonce
  console.log('Tokens received:', result);
}

// Llamar a /userinfo con DPoP
const userInfoResponse = await fetch('https://[TENANT]/userinfo', {
  method: 'GET',
  headers: {
    // Enviar el token de acceso usando el esquema de autorización DPoP
    Authorization: `DPoP ${result.access_token}`,

    // Incluir un encabezado DPoP, esta vez con el hash del token de acceso
    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());

Paso 6: Gestionar la renovación de tokens con DPoP

Cuando tu token de acceso vinculado a DPoP caduque, puedes usar un para obtener uno nuevo. Una solicitud de token de actualización requiere un JWT de prueba de DPoP generado con el mismo par de claves usado en la solicitud de token original. A continuación se describe el flujo de actualización de token con DPoP en Auth0: La aplicación cliente:
  • Realiza una solicitud de token de actualización al endpoint /token del Servidor de autorización de Auth0.
  • Genera un JWT de prueba de DPoP para la solicitud de token de actualización (similar al Paso 2, con htm como POST y htu como el URI del ).
  • Incluye el JWT de prueba de DPoP en el encabezado HTTP DPoP.
El Servidor de autorización de Auth0:
  • Valida el JWT de prueba de DPoP (como en el Paso 4) y emite un nuevo token de acceso vinculado a DPoP.

Consideraciones importantes

Al implementar DPoP en sus aplicaciones cliente, tenga en cuenta lo siguiente:
  • Seguridad de la clave privada: La seguridad de su implementación de DPoP depende de la seguridad de la clave privada de su cliente, por lo que debe protegerla del acceso no autorizado. Las claves privadas deben generarse y almacenarse en un medio con respaldo de hardware y marcarse como no exportables.
  • Protección contra ataques de repetición (jti** y dpop-nonce):** El claim jti del JWT de prueba de DPoP ayuda a prevenir ataques de repetición en recursos protegidos, como el endpoint /userinfo. Actualmente, el Servidor de autorización de Auth0 no comprueba la reutilización de jti en el endpoint /userinfo. El Servidor de autorización de Auth0 emite un encabezado HTTP DPoP-Nonce en la respuesta, que los clientes públicos deben incluir como claim nonce en los JWT de prueba de DPoP posteriores para reforzar la protección contra ataques de repetición.
  • Límites de frecuencia: Como el flujo de desafío-respuesta de DPoP a veces puede requerir una solicitud inicial seguida de un reintento con el nonce proporcionado por el servidor, cada intercambio cuenta en la práctica como dos solicitudes para los límites de frecuencia de su tenant de Auth0. Asegúrese de que el volumen de solicitudes de su aplicación contemple esta sobrecarga.
  • Manejo de errores: Usted es responsable de implementar la lógica para manejar errores específicos de DPoP del Servidor de autorización de Auth0 o del servidor de recursos, como invalid_dpop_proof o use_dpop_nonce.
  • Tipos de cliente: Use DPoP para clientes públicos, como Single Page Applications (SPA) o aplicaciones móviles que no pueden almacenar de forma segura un Secreto del cliente. Para , como los servicios backend con secretos del cliente, DPoP añade una capa adicional de seguridad, pero ya cuentan con otros mecanismos para restringir el remitente.
  • Rendimiento: Dado que generar y firmar JWT de prueba de DPoP para cada llamada a la API añade una pequeña sobrecarga, asegúrese de que las operaciones criptográficas de su aplicación cliente sean eficientes.
  • Rotación de claves: Implemente una estrategia para rotar sus pares de claves de DPoP y así mejorar la seguridad. Asegúrese de usar el mismo par de claves durante la misma sesión.
  • Persistencia: En las aplicaciones cliente que necesitan mantener una sesión y reutilizar tokens de acceso vinculados a DPoP, como las SPA de larga duración, conserve y recupere de forma segura el par de claves original entre recargas de la aplicación. Si se genera un nuevo par de claves o se usa uno diferente, el token de acceso vinculado a DPoP deja de ser válido, ya que está vinculado criptográficamente a la clave pública del par original. Puede conservar el par de claves, por ejemplo, en IndexedDB del navegador o en el almacenamiento seguro de una aplicación móvil.

Más información