OID4VP — protocolo de presentación de credenciales
OID4VP —OpenID for Verifiable Presentations— es el protocolo HTTP estándar para que un verificador solicite credenciales a una wallet y reciba la presentación correspondiente. Es el complemento de OID4VCI (que emite) y el otro pilar de la familia OID4VC.
Está mantenido por OpenID Foundation, es obligatorio en eIDAS 2.0 (UE), y es el protocolo de presentación canonical en el stack Sovra.
El flujo same-device
El caso más simple: ciudadano accede a un sitio web (verificador) desde el mismo dispositivo donde tiene la wallet:
- 1Verificador genera un request. Crea un
presentation_definition(qué credenciales quiere) + unrequest_urique la wallet puede consultar. - 2Verificador muestra QR o deep link. El ciudadano hace click o escanea.
- 3Wallet abre el deep link. Recupera el
presentation_definitiondesde elrequest_uri. - 4Wallet evalúa credenciales. Identifica qué credenciales del usuario matchean los requirements.
- 5Usuario consiente. Wallet muestra qué se va a compartir y pide confirmación.
- 6Wallet arma Verifiable Presentation. Combina las credenciales + key binding del holder.
- 7Wallet envía la VP al verifier. POST a un endpoint de
response_uri. - 8Verifier valida. Firma del holder + firma de cada credencial + estado de revocación.
El flujo cross-device
Variante donde el ciudadano está en una computadora (verificador) y usa la wallet en su celular:
- Verificador muestra QR en la pantalla.
- Ciudadano escanea con app de wallet en el celular.
- El resto del flujo es similar pero la confirmación + presentación viene desde el celular.
La estructura de un presentation_definition
{
"id": "alquiler-auto-2026",
"input_descriptors": [
{
"id": "licencia-conducir",
"name": "Licencia de conducir vigente",
"purpose": "Confirmar habilitación de conducir",
"constraints": {
"fields": [
{
"path": ["$.credentialSubject.tipo"],
"filter": {
"type": "string",
"const": "B1"
}
},
{
"path": ["$.credentialSubject.validUntil"],
"filter": {
"type": "string",
"format": "date"
}
}
]
}
}
]
}
Esto se traduce como: "El verifier quiere una credencial donde el campo tipo sea exactamente B1 y donde haya un campo validUntil con formato fecha".
Selective disclosure en OID4VP
OID4VP no implementa selective disclosure directamente — eso lo hace el formato (SD-JWT VC con sus disclosures, o ISO 18013-5 con su mecánica binaria).
Pero OID4VP permite al verificador especificar exactamente qué campos pide. La wallet sabe entonces qué disclosures revelar y qué dejar oculto.
Ejemplo de respuesta
La wallet responde con una Verifiable Presentation:
{
"vp_token": "eyJhbGciOi...~disclosure1~disclosure2~",
"presentation_submission": {
"id": "submission-123",
"definition_id": "alquiler-auto-2026",
"descriptor_map": [
{
"id": "licencia-conducir",
"format": "vc+sd-jwt",
"path": "$"
}
]
},
"state": "abc123"
}
El vp_token es la credencial efectiva (SD-JWT VC). El presentation_submission describe cómo el verifier debe interpretar el token.
Implementación del Verifier
// Genera el request
async function generatePresentationRequest(definition: PresentationDefinition) {
const requestUri = `https://${origin}/oid4vp/request/${generateId()}`;
await storeRequest(requestUri, {
presentation_definition: definition,
nonce: generateNonce(),
response_uri: `https://${origin}/oid4vp/response`,
expires_in: 600,
});
return {
deep_link: `openid4vp://?request_uri=${requestUri}`,
request_uri: requestUri,
};
}
// Recibe la respuesta
async function handlePresentationResponse(req: Request) {
const body = await req.json();
const vpToken = body.vp_token;
const submission = body.presentation_submission;
// Verificar firma del holder (key binding)
const holderDid = extractHolderDid(vpToken);
const isValidBinding = verifyKeyBinding(vpToken, expectedNonce);
if (!isValidBinding) return error(400, "invalid_key_binding");
// Verificar cada credencial dentro de la presentation
for (const descriptor of submission.descriptor_map) {
const credential = extractCredential(vpToken, descriptor);
// Validar firma del issuer
const isValid = await verifyCredentialSignature(credential);
if (!isValid) return error(400, "invalid_credential");
// Validar estado de revocación
const isRevoked = await checkRevocationStatus(credential);
if (isRevoked) return error(400, "credential_revoked");
// Verificar contra el presentation_definition
const matchesRequirements = matchesDefinition(credential, descriptor);
if (!matchesRequirements) return error(400, "constraint_violation");
}
// Acceder a los atributos verificados
const verifiedAttributes = extractAttributes(vpToken);
return success(verifiedAttributes);
}
El campo nonce — anti-replay
Cada presentation request incluye un nonce único. La wallet lo incluye en el JWT del key binding. Sin esto, una presentation interceptada puede ser replicada después. Con nonce, cada presentation es one-shot.
OID4VP vs OID4VCI
| OID4VCI | OID4VP | |
|---|---|---|
| Dirección | Issuer → Holder | Holder → Verifier |
| Firma | Issuer (en VC) | Holder (en VP) |
| Caso típico | Recibir credencial nueva | Probar atributo a un verifier |
| Cuándo se invoca | Después de un trámite | Antes de un servicio |
Ambos comparten infraestructura OAuth 2.0 y son complementarios.
Adopción
OID4VP es soportado por:
eIDAS 2.0 UE
Obligatorio para EUDI Wallet.
Sovra
Canonical en Sovra Wallet + SovraID Verifier.
Microsoft / Google
Adoptado en Entra y Wallet.
Referencias
Relacionados
- Cómo emite credenciales un sistema con OID4VCI — el protocolo complementario de emisión
- DCQL — Digital Credentials Query Language — DCQL queries en OID4VP
- DIDComm v2 — mensajería entre DIDs — el protocolo peer-to-peer alternativo

