Volver al curso
PatrónTécnicoIdentidad y CVIntermedio

OID4VP — protocolo de presentación de credenciales

8 minVerificado · 2026-05-18

OID4VPOpenID 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:

  1. 1
    Verificador genera un request. Crea un presentation_definition (qué credenciales quiere) + un request_uri que la wallet puede consultar.
  2. 2
    Verificador muestra QR o deep link. El ciudadano hace click o escanea.
  3. 3
    Wallet abre el deep link. Recupera el presentation_definition desde el request_uri.
  4. 4
    Wallet evalúa credenciales. Identifica qué credenciales del usuario matchean los requirements.
  5. 5
    Usuario consiente. Wallet muestra qué se va a compartir y pide confirmación.
  6. 6
    Wallet arma Verifiable Presentation. Combina las credenciales + key binding del holder.
  7. 7
    Wallet envía la VP al verifier. POST a un endpoint de response_uri.
  8. 8
    Verifier 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

OID4VCIOID4VP
DirecciónIssuer → HolderHolder → Verifier
FirmaIssuer (en VC)Holder (en VP)
Caso típicoRecibir credencial nuevaProbar atributo a un verifier
Cuándo se invocaDespués de un trámiteAntes 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

Tagsoid4vppresentationopenidverifier