For the complete documentation index, see llms.txt. This page is also available as Markdown.

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