Skip to Content

DPoP (Demostración de prueba de posesión)

El problema con los Bearer Tokens

La especificación OAuth 2.0 Bearer Token (RFC 6750) define tokens que conceden acceso a cualquiera que los posea. La palabra “bearer” es literal: quien porta (tiene) el token puede usarlo. No hay verificación de que el presentador del token sea la misma parte a quien se emitió.

Esto crea una clase de ataques:

AtaqueMecanismo
Exfiltración de token via XSSJavaScript malicioso lee el token de localStorage y lo envía al servidor del atacante
Filtración de token via logsAccess tokens registrados accidentalmente por proxies, CDNs o servidores de aplicación
Repetición de tokenUn token interceptado se repite desde un dispositivo o IP diferente
Robo de token via middleware comprometidoUn proxy inverso o API gateway almacena tokens para uso posterior

Qué hace DPoP

DPoP (Demonstrating Proof of Possession), definido en RFC 9449, resuelve este problema vinculando los tokens al par de claves criptográficas del cliente. Un token vinculado con DPoP es inútil sin la clave privada correspondiente.

La idea central:

  1. El cliente genera un par de claves efímero (RSA o EC)
  2. Al solicitar un token, el cliente crea una prueba DPoP — un JWT firmado con la clave privada
  3. El servidor de autorización (Auris) extrae la clave pública de la prueba y vincula el token emitido a esa clave mediante un JWK thumbprint (claim jkt)
  4. En cada llamada a la API, el cliente envía tanto el access token vinculado como una prueba DPoP fresca firmada con la misma clave privada
  5. El resource server verifica que la clave pública de la prueba coincide con el binding jkt del token

Si un atacante roba el access token pero no la clave privada (que se mantiene en memoria y nunca se transmite), el token es inútil.

Cómo funciona: paso a paso

Paso 1: El cliente genera un par de claves efímero

const keyPair = await crypto.subtle.generateKey( { name: 'ECDSA', namedCurve: 'P-256' }, false, // no extraíble: la clave privada no puede exportarse ['sign', 'verify'] )

Paso 2: El cliente crea una prueba DPoP JWT

// Encabezado de la prueba DPoP JWT { "typ": "dpop+jwt", "alg": "ES256", "jwk": { "kty": "EC", "crv": "P-256", "x": "coordenada-x-codificada-base64url", "y": "coordenada-y-codificada-base64url" } } // Payload de la prueba DPoP JWT { "jti": "id-prueba-unico-abc123", "htm": "POST", "htu": "https://auth.example.com/api/auth/token", "iat": 1739880000, "nonce": "nonce-proporcionado-por-servidor" }

Paso 3: La prueba se envía con la solicitud de token

POST /api/auth/token HTTP/1.1 DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7... Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=...&code_verifier=...

Paso 4: Auris vincula el token emitido a la clave

El access token emitido contiene el claim cnf.jkt:

{ "sub": "usr_abc123", "cnf": { "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" } }

El jkt es el thumbprint SHA-256 de la JWK pública del cliente.

Paso 5: En cada solicitud a la API

GET /api/users/me HTTP/1.1 Authorization: DPoP eyJhbGciOiJSUzI1NiJ9... DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7...

Auris admite DPoP como característica opt-in. Habilítalo en Consola → Aplicaciones → [tu app] → Seguridad avanzada → Requerir DPoP. Una vez habilitado, todas las solicitudes de token desde esa aplicación deben incluir una prueba DPoP.

DPoP vs mTLS

Ambos vinculan tokens a claves criptográficas del cliente, pero con diferentes trade-offs:

AspectoDPoPmTLS
Infraestructura requeridaNinguna (usa Web Crypto API)Certificados de cliente + PKI
Funciona en browsersSíNo (los browsers no exponen certificados de cliente a JS)
Gestión de clavesEfímero, por sesiónCertificados de larga duración que deben renovarse
RevocaciónInmediata (la clave privada está en memoria)Requiere CRL o OCSP
Resistencia a exfiltración de tokenAltaAlta
Complejidad de implementaciónBajaAlta