Saltar al contenido principal

Flujo de emisión de credenciales

El Credential Issuer de ISBE es el servicio encargado de emitir IsbePortalLearCredential a los empleados y representantes de las organizaciones participantes en la red. El flujo se basa en el protocolo OID4VCI (OpenID for Verifiable Credential Issuance) con preauthorization code.

Arquitectura del Credential Issuer

Portal ISBE
│ (token Keycloak)

Credential Issuer API ◄──── TMF API (datos de la organización)
│ ◄──── Management API (roles y poderes autorizados)

Wallet del usuario (escaneado QR)


Credential Issuer API ──► Email al solicitante (QR de oferta de credencial)

El Issuer está implementado en Django + Django REST Framework. El Issuer valida permisos, registra el estado, coordina la generación de la credencial y entrega el QR al solicitante.

Tipos de emisión

El Issuer soporta dos tipos de emisión de credenciales:

TipoEndpointDescripción
RepresentantePOST /issuance/representativeEmite una VC al propio representante autenticado (el solicitante y el portador son la misma persona). El QR se devuelve en la respuesta HTTP.
EmpleadoPOST /issuance/employeeEmite una VC a un empleado de la organización. El QR se envía por email al empleado.

Flujo paso a paso: emisión de credencial de representante

1. Autenticación del representante

El representante se autentica en el portal ISBE. Keycloak devuelve un token JWT (access token) con los siguientes claims relevantes:

  • organization_identifier — Identificador fiscal de la organización (p. ej. ES-B12345678).
  • organization — Nombre de la organización.
  • user_identifier — ID del empleado en el sistema de la organización.
  • email — Email del representante.
  • power — Array de poderes autorizados por ISBE para esa organización.

2. Solicitud de credencial (POST /issuance/representative)

El portal llama al Issuer con el token de Keycloak en la cabecera Authorization: Bearer <token>:

{
"power": [
{
"type": "domain",
"domain": "ISBE",
"function": "onboarding",
"action": ["execute"]
}
]
}

El Issuer realiza las siguientes comprobaciones antes de emitir:

  1. Valida el token de Keycloak verificando la firma contra el JWKS endpoint de Keycloak (KEYCLOAK_JWKS_URI).
  2. Verifica los poderes solicitados consultando la Management API: los poderes incluidos en el body del POST deben ser un subconjunto de los poderes autorizados por ISBE para organization_identifier.
  3. Determina el tipo de VC (vc_type) a partir de la tabla Configuration de la base de datos (key = VC_TYPES, tag = "representative").
  4. Determina el perfil de emisión a usar (tabla Configuration, key = PROFILE).

3. Registro del preauth code

El servicio de emisión genera un preauth code con los siguientes parámetros:

{
"preauth_code": "52c520b0-b0b6-40c7-8c62-d17b1cce920f",
"expires_in": 300
}

4. Generación del QR

El servicio de emisión genera una imagen PNG con el QR de la oferta de credencial.

5. Persistencia del estado

El Issuer crea un registro IssuedCredential en la base de datos con:

CampoValor
vc_typeTipo de VC (p. ej. IsbePortalLearCredential)
subject_id<org_identifier>_<timestamp>
organization_identifierIdentificador fiscal de la organización
preauth_codeUUID del preauth code
preauth_code_expires_innow() + expires_in
statuspending
token_dataClaims del token de Keycloak
body_dataBody del POST (poderes solicitados)
credential_type"representative"
employee_iduser_identifier del token

6. Entrega del QR al solicitante

  • Si el token incluye un campo email, el Issuer también envía un email al representante con el QR embebido.
  • La respuesta HTTP al portal devuelve la imagen PNG directamente (Content-Type: image/png).

7. El usuario escanea el QR con su wallet

El representante escanea el QR con su wallet digital compatible con OID4VCI. El wallet inicia el proceso de reclamación de la credencial; el Issuer recibe la solicitud y responde con los claims:

POST /issuance/claims
{
"preauth_code": "52c520b0-b0b6-40c7-8c62-d17b1cce920f"
}

El Issuer busca el IssuedCredential por preauth_code, lee los datos de organización del TMF API y responde con los claims que debe incluir la VC en el credentialSubject.

8. Notificación de éxito

Cuando el wallet recibe la VC correctamente, el Issuer es notificado:

POST /issuance/notifications
{
"preauth_code": "...",
"credential_id": "urn:uuid:...",
"status": "issued"
}

El Issuer actualiza el registro IssuedCredential a status = "issued" y guarda el credential_id.

Flujo de emisión de credencial de empleado

El flujo de emisión de empleado (POST /issuance/employee) es similar al de representante con estas diferencias:

  • El body del POST incluye el email del empleado destinatario y los poderes a asignarle.
  • El QR no se devuelve en la respuesta HTTP; en su lugar se envía por email al empleado directamente.
  • La respuesta al portal es un JSON con un mensaje de confirmación de envío.
Permisos para emitir credenciales de empleado

Para emitir credenciales a empleados, el representante autenticado debe tener en su token de Keycloak un poder que incluya la función de emisión delegada. La lógica de validación de poderes es la misma que en la emisión de representante.

Estado de una credencial emitida

El ciclo de vida de un IssuedCredential sigue estos estados:

EstadoDescripción
pendingEl preauth code ha sido creado, el wallet aún no ha reclamado la credencial.
issuedEl wallet reclamó la credencial y el Connector emisión confirmada.
revokedLa credencial fue revocada mediante POST /issuance/credential/revoke.

Consultar y revocar credenciales

EndpointDescripción
POST /issuance/credentialRecupera el listado de credenciales emitidas para una o varias organizaciones. Protegido con token Keycloak.
POST /issuance/credential/revokeRevoca una credencial a partir de su credential_id. Invalida la VC en el registro de estado.
POST /issuance/identifiersDevuelve los vc_types para los que el subject_id indicado tiene permisos de emisión.
La revocación es permanente

Una vez revocada una credencial, no puede volver a activarse. Es necesario emitir una nueva credencial si el empleado o representante necesita seguir accediendo.