Frontend for Credential Issuance
What We Are Doing:
Creating a simple HTML form to collect the holder's details.
When the form is submitted, it calls our backend route, which asks the Issuer for a credential offer and returns the offer data, including a URL for a QR code image.
The user scans this QR code with a compatible wallet to claim the credential.
Why: A user interface is essential so that a real person can enter their details and receive a credential. The browser never talks to the Issuer directly — it talks to our backend, which holds the access token and shapes the request.
How the QR works: This is the two-step flow we built on the Issuing Credentials page. First the form POSTs to our backend route /issuer/kyc-credential, which creates the offer at the Issuer and responds with JSON — { credentialOfferUri, issuanceSessionId, qr_code_url }. The qr_code_url points at our own backend route GET /issuer/kyc-credential/:sessionId/qr-code, which proxies the PNG image of the QR code. So the frontend reads the POST response as JSON, then simply points an <img> at the returned qr_code_url to load the QR. The QR encodes an openid-credential-offer://... URI; scanning it tells the wallet where to claim the credential.
Steps: In public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>EVDI Tutorial</title>
</head>
<body>
<h1>KYC Credential Issuance</h1>
<form id="issuance-form">
<label for="firstName">First Name</label>
<input type="text" name="firstName" id="firstName" required/>
<label for="lastName">Last Name</label>
<input type="text" name="lastName" id="lastName" required/>
<label for="age">Age</label>
<input type="number" name="age" id="age" required/>
<button type="submit">Create Credential Offer</button>
</form>
<img src="" alt="Credential offer QR code" width="300" height="300" id="qrcode" style="display:none;"/>
<script>
const form = document.getElementById('issuance-form');
const qrcodeImg = document.getElementById('qrcode');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
// Field names match what our backend route destructures from req.body.
const payload = {
firstName: formData.get('firstName'),
lastName: formData.get('lastName'),
age: formData.get('age')
};
// Our backend creates the offer at the Issuer and returns JSON,
// including a qr_code_url pointing at our own QR-proxy route.
const res = await fetch('/issuer/kyc-credential', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
if (!res.ok) {
console.error('Failed to create offer:', res.status);
return;
}
// The response is JSON: { credentialOfferUri, issuanceSessionId, qr_code_url }.
// Point the <img> at qr_code_url to load the QR PNG from our backend.
const { qr_code_url } = await res.json();
qrcodeImg.src = qr_code_url;
qrcodeImg.style.display = 'block';
});
</script>
</body>
</html>The backend routes are the ones we built on the Issuing Credentials page. The POST /issuer/kyc-credential route maps the submitted fields into the offer's issuanceMetadata.claimsByConfigurationId for [email protected]:sd-jwt, calls the Issuer's credential-offers endpoint with the operator's Bearer token, and responds with { credentialOfferUri, issuanceSessionId, qr_code_url }. The browser then loads qr_code_url (our GET /issuer/kyc-credential/:sessionId/qr-code route), which fetches the Issuer's GET /issuance-sessions/:sessionId/qr-code PNG with the same Bearer token and streams it straight back to the <img>.
Now you can test issuing a credential by filling out the form and scanning the generated QR code with a compatible wallet. The wallet reads the embedded openid-credential-offer:// URI, claims the [email protected]:sd-jwt credential, and stores it.
Last updated