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:
| Type | Endpoint | Description |
|---|---|---|
| Representative | POST /issuance/representative | Issues a VC to the authenticated representative themselves (applicant and holder are the same person). The QR is returned in the HTTP response. |
| Employee | POST /issuance/employee | Issues 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:
- Validates the token by verifying the Keycloak signature against the Keycloak JWKS endpoint (
KEYCLOAK_JWKS_URI). - 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. - Determines the VC type (
vc_type) from theConfigurationdatabase table (key = VC_TYPES, tag = "representative"). - 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:
| Field | Value |
|---|---|
vc_type | VC type (e.g. IsbePortalLearCredential) |
subject_id | <org_identifier>_<timestamp> |
organization_identifier | Organization's tax identifier |
preauth_code | UUID of the preauth code |
preauth_code_expires_in | now() + expires_in |
status | pending |
token_data | Keycloak token claims |
body_data | POST body (requested powers) |
credential_type | "representative" |
employee_id | user_identifier from the token |
6. QR delivery to the applicant
- If the token includes an
emailfield, 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.
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:
| State | Description |
|---|---|
pending | The preauth code has been created; the wallet has not yet claimed the credential. |
issued | The wallet claimed the credential and issuance confirmed. |
revoked | The credential was revoked via POST /issuance/credential/revoke. |
Query and revoke credentials
| Endpoint | Description |
|---|---|
POST /issuance/credential | Retrieves the list of issued credentials for one or more organizations. Protected with a Keycloak token. |
POST /issuance/credential/revoke | Revokes a credential by its credential_id. Invalidates the VC in the status registry. |
POST /issuance/identifiers | Returns the vc_types for which the indicated subject_id has issuance permissions. |
Once a credential is revoked, it cannot be reactivated. A new credential must be issued if the employee or representative needs to continue accessing.