Skip to main content

Credential Issuance Flow

The ISBE Credential Issuer is the service responsible for issuing IsbePortalLearCredential credentials to employees and representatives of organizations participating in the network. The flow is based on the OID4VCI (OpenID for Verifiable Credential Issuance) protocol with preauthorization code.

Credential Issuer architecture

ISBE Portal
│ (Keycloak token)

Credential Issuer API ◄──── TMF API (organization data)
│ ◄──── Management API (authorized roles and powers)


Credential Issuer API ──► Email to applicant (credential offer QR)

The Issuer is implemented in Django + Django REST Framework. The Issuer validates permissions, records the state, coordinates credential generation, and delivers the QR to the applicant.

Issuance types

The Issuer supports two types of credential issuance:

TypeEndpointDescription
RepresentativePOST /issuance/representativeIssues a VC to the authenticated representative themselves (applicant and holder are the same person). The QR is returned in the HTTP response.
EmployeePOST /issuance/employeeIssues a VC to an employee of the organization. The QR is sent by email to the employee.

Step-by-step flow: representative credential issuance

1. Representative authentication

The representative authenticates in the ISBE portal. Keycloak returns a JWT token (access token) with the following relevant claims:

  • organization_identifier — Tax identifier of the organization (e.g. ES-B12345678).
  • organization — Organization name.
  • user_identifier — Employee ID in the organization's system.
  • email — Representative's email.
  • power — Array of powers authorized by ISBE for that organization.

2. Credential request (POST /issuance/representative)

The portal calls the Issuer with the Keycloak token in the Authorization: Bearer <token> header:

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

The Issuer performs the following checks before issuing:

  1. Validates the token by verifying the Keycloak signature against the Keycloak JWKS endpoint (KEYCLOAK_JWKS_URI).
  2. Verifies the requested powers by querying the Management API: the powers included in the POST body must be a subset of the powers authorized by ISBE for the organization_identifier.
  3. Determines the VC type (vc_type) from the Configuration database table (key = VC_TYPES, tag = "representative").
  4. Determines the issuance profile to use (table Configuration, key = PROFILE).

3. Preauth code registration

The issuance service generates a preauth code with the following parameters:

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

4. QR generation

The issuance service generates a PNG image with the credential offer QR.

5. State persistence

The Issuer creates an IssuedCredential record in the database with:

FieldValue
vc_typeVC type (e.g. IsbePortalLearCredential)
subject_id<org_identifier>_<timestamp>
organization_identifierOrganization's tax identifier
preauth_codeUUID of the preauth code
preauth_code_expires_innow() + expires_in
statuspending
token_dataKeycloak token claims
body_dataPOST body (requested powers)
credential_type"representative"
employee_iduser_identifier from the token

6. QR delivery to the applicant

  • If the token includes an email field, the Issuer also sends an email to the representative with the embedded QR.
  • The HTTP response to the portal returns the PNG image directly (Content-Type: image/png).

7. The user scans the QR with their wallet

The representative scans the QR with their OID4VCI-compatible digital wallet. The wallet initiates the credential claim process; the Issuer receives the request and responds with the claims:

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

The Issuer looks up the IssuedCredential by preauth_code, reads the organization data from the TMF API, and responds with the claims to include in the VC's credentialSubject.

8. Success notification

When the wallet successfully receives the VC, the Issuer is notified:

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

The Issuer updates the IssuedCredential record to status = "issued" and saves the credential_id.

Employee credential issuance flow

The employee issuance flow (POST /issuance/employee) is similar to the representative flow with these differences:

  • The POST body includes the destination employee's email and the powers to assign.
  • The QR is not returned in the HTTP response; instead it is sent directly by email to the employee.
  • The portal response is a JSON with a send confirmation message.
Permissions to issue employee credentials

To issue credentials to employees, the authenticated representative must have in their Keycloak token a power that includes the delegated issuance function. The power validation logic is the same as for representative issuance.

Issued credential lifecycle

The lifecycle of an IssuedCredential follows these states:

StateDescription
pendingThe preauth code has been created; the wallet has not yet claimed the credential.
issuedThe wallet claimed the credential and issuance confirmed.
revokedThe credential was revoked via POST /issuance/credential/revoke.

Query and revoke credentials

EndpointDescription
POST /issuance/credentialRetrieves the list of issued credentials for one or more organizations. Protected with a Keycloak token.
POST /issuance/credential/revokeRevokes a credential by its credential_id. Invalidates the VC in the status registry.
POST /issuance/identifiersReturns the vc_types for which the indicated subject_id has issuance permissions.
Revocation is permanent

Once a credential is revoked, it cannot be reactivated. A new credential must be issued if the employee or representative needs to continue accessing.