Issuance
This page shows you how to use the JavaScript client to issue credentials. For detailed info on credential issuance, refer to the Issuance guide.
Authentication
All applications that issue credentials must authenticate users and provide an identity reference for the issuance token. Refer to the Authorization guide for further info. The complete example below uses Microsoft authorization, however there is no restriction on where identity references are sourced.
Issuance steps
Issuance is a multi-step process described in the Issuance guide.
1. Acquire an access token
Obtain a limited access token for issuances from your secure backend API. Refer to the Limited access tokens guide for more info.
let apiAccessToken, verifiedOrchestrationClient
// obtain an access token to call your API
msalInstance.acquireTokenSilent({ scopes: ['api://<your-backend-API>/.default'] }).then((response) => {
apiAccessToken = response.accessToken
// call your backend API for an issuance token
acquireIssuanceToken().then((data) => {
// contruct a VerifiedOrchestrationClient with the issuance token
verifiedOrchestrationClient = new verifiedOrchestrationClientJs.VerifiedOrchestrationClient({
url: '<Verified Orchestration API URL>',
accessToken: data.token,
})
// begin issuance
issue()
})
})
2. Create an issuance request
Create an issuance request specifying the contract of the credential to be issued and optionally (not shown in this example):
- the values of any claims defined in the contract
- a PIN code that the recipient must enter to retrieve the credential (best sent via a separate channel)
- callback info, if issuance event data should also be sent to a server-side endpoint
const contract = 'TODO: fill this in'
let pin
function issue() {
app.innerHTML = 'Generating issuance ...'
pin = [...Array(4)].map(() => Math.floor(Math.random() * 10)).join('')
verifiedOrchestrationClient
.createIssuanceRequest({ contractId, pin }, onIssuanceEvent)
.then((response) => onIssuance(response))
.catch(displayError)
}
3. Display QR code or open link
If the user is on a mobile device, open the issuance link in the browser. Otherwise, display the QR code for the user to scan.
function onIssuance(response) {
if ('error' in response) {
displayError(response.error)
return
}
app.innerHTML = `<div>Scan to install credential</div>`
const { qrCode, url } = response
displayQRCodeOrOpenLink(qrCode, url)
}
function displayQRCodeOrOpenLink(qrcode, url) {
const isMobile = /Android|iPhone/i.test(navigator.userAgent)
if (isMobile) {
window.location.href = url
} else {
app.innerHTML += `<img src="${qrcode}" alt="${url}" />`
}
}
4. Relay PIN to recipient (optional)
If a PIN was specified in the issuance request, it must be entered by the recipient. Ideally, the PIN should be sent via a separate channel (e.g. via SMS or email), but for this example we show it on screen once the issuance request is opened.
function onIssuanceEvent(data) {
if (data instanceof Error) {
displayError(data)
} else if (data.event.requestStatus === 'request_retrieved') {
// Consider relaying the PIN via a separate channel. This example displays the PIN on screen.
app.innerHTML = `<div>Enter verification code ${pin} to complete issuance...</div>`
} else {
app.innerHTML = `
<div>Issuance complete ✅</div>
<button onClick="showIssuanceUI()">Issue another</button>
`
}
}
5. Receive issuance notification
Upon completion of issuance, the event.requestStatus
will be issuance_successful
and event.issuance
will contain data (see the Issuance
type for details).
function onIssuanceEvent(data) {
if (data instanceof Error) {
displayError(data)
} else if (data.event.requestStatus === 'request_retrieved') {
app.innerHTML = `<div>Enter verification code ${pin} to complete issuance...</div>`
} else {
// Handle issuance_complete and data.event.issuance. This example reloads the UI.
app.innerHTML = `
<div>Issuance complete ✅</div>
<button onClick="showIssuanceUI()">Issue another</button>
`
}
}
6. Optional: photo capture issuance request
For contracts that require a face check photo for issuance, a photo can be captured before issuing the credential via the createPhotoCaptureIssuance
function.
This function will return a QR code and URL which can be opened on any device to capture a photo.
Once the photo has been captured, issuance will proceed as normal.
function issue() {
const contract = 'TODO: fill this in'
if (contract.faceCheckSupport) {
app.innerHTML = 'Generating photo capture issuance...'
verifiedOrchestrationClient
.createPhotoCaptureIssuance({ contractId, callback }, onPhotoCaptureEvent, onIssuance, onIssuanceEvent)
.then((response) => onPhotoCapture(response))
.catch(displayError)
}
}
function onPhotoCaptureEvent(data) {
if (data instanceof Error) {
displayError(data)
} else if (data.status === 'complete') {
app.innerHTML += '<div>Photo Capture is complete ✅</div>'
}
}
function onPhotoCapture(response) {
if ('error' in response) {
displayError(response.error)
return
}
app.innerHTML = `<div>Scan to capture photo</div>`
const { photoCaptureQrCode, photoCaptureUrl } = response
displayQRCodeOrOpenLink(photoCaptureQrCode, photoCaptureUrl)
}
Complete example
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Issue Credentials</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@verified-orchestration/client-js/index.min.js"></script>
<script type="text/javascript" src="https://alcdn.msauth.net/browser/2.38.2/js/msal-browser.min.js"></script>
</head>
<body>
<div id="app">Loading...</div>
<script type="text/javascript">
/* The contracts of credentials that this client can issue (to the logged-in user).
Example of prefilled contracts:
const contracts = [
{ id: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', faceCheckSupport: true },
{ id: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' }
]
*/
const contracts = 'TODO: fill this in'
const app = document.getElementById('app')
let msalInstance, apiAccessToken, verifiedOrchestrationClient, pin, photoCaptureRequestId
function displayError(error) {
app.innerHTML = ``
}
function showIssuanceUI() {
app.innerHTML = `
`
}
// Create issuance request
function issue() {
const selectedId = document.getElementById('contractId').value
const contract = contracts.find((contract) => contract.id === selectedId)
const contractId = contract.id
const callback = { url: 'https://<your-callback-url>' }
pin = Array.from({ length: 4 }, () => Math.floor(Math.random() * 10)).join('')
if (contract.faceCheckSupport) {
app.innerHTML = 'Generating photo capture issuance...'
verifiedOrchestrationClient
.createPhotoCaptureIssuance({ contractId, pin, callback }, onPhotoCaptureEvent, onIssuance, onIssuanceEvent)
.then((response) => onPhotoCapture(response))
.catch(displayError)
} else {
app.innerHTML = 'Generating issuance...'
verifiedOrchestrationClient
.createIssuanceRequest({ contractId, pin, callback }, onIssuanceEvent)
.then((response) => onIssuance(response))
.catch(displayError)
}
}
function displayQRCodeOrOpenLink(qrcode, url) {
const isMobile = /Android|iPhone/i.test(navigator.userAgent)
if (isMobile) {
window.location.href = url
} else {
app.innerHTML += ``
}
}
function onPhotoCaptureEvent(data) {
if (data instanceof Error) {
displayError(data)
} else if (data.status === 'complete') {
app.innerHTML += '<div>Photo Capture is complete ✅</div>'
}
}
function onPhotoCapture(response) {
if ('error' in response) {
displayError(response.error)
return
}
app.innerHTML = ``
const { photoCaptureQrCode, photoCaptureUrl } = response
displayQRCodeOrOpenLink(photoCaptureQrCode, photoCaptureUrl)
}
function onIssuanceEvent(data) {
if (data instanceof Error) {
displayError(data)
} else if (data.event.requestStatus === 'request_retrieved') {
app.innerHTML = ``
} else {
app.innerHTML = `
`
}
}
function onIssuance(response) {
if ('error' in response) {
displayError(response.error)
return
}
app.innerHTML = ``
const { qrCode, url } = response
displayQRCodeOrOpenLink(qrCode, url)
}
// Acquire an issuance token from a secure backend API,
// the issuance token allows a fixed set of credentials to be issued to the logged-in user,
// our backend API also creates a reference to the logged-in user, which is fixed for the issuance token
async function acquireIssuanceToken() {
app.innerHTML = 'Acquiring identity-based token for issuance...'
const response = await fetch('https://<your-backend-API>/acquireTokenForIssuance', {
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiAccessToken}` },
method: 'POST',
})
return response.json()
}
// MSAL authentication - set up MSAL and log in
function initializeMsal() {
msalInstance = new msal.PublicClientApplication({
auth: {
clientId: '<client ID>',
authority: 'https://login.microsoftonline.com/<tenant-ID>/v2.0',
knownAuthorities: ['https://login.microsoftonline.com/<tenant-ID>'],
navigateToLoginRequestUrl: true,
},
cache: {
cacheLocation: 'sessionStorage',
claimsBasedCachingEnabled: true,
},
})
msalInstance.addEventCallback(({ eventType, payload }) => {
if (eventType === msal.EventType.LOGIN_SUCCESS && payload && payload.account) {
msalInstance.setActiveAccount(payload.account)
}
})
msalInstance.handleRedirectPromise().then(() => {
const accounts = msalInstance.getAllAccounts()
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0])
// now we are logged in
// obtain an access token to call your API
msalInstance
.acquireTokenSilent({
scopes: ['api://<your-backend-API>/.default'],
})
.then((response) => {
apiAccessToken = response.accessToken
// call your backend API for an issuance token
acquireIssuanceToken().then((data) => {
// construct a VerifiedOrchestrationClient with the issuance token
verifiedOrchestrationClient = new verifiedOrchestrationClientJs.VerifiedOrchestrationClient({
url: '<Verified-Orchestration-API-URL>',
accessToken: data.token,
})
// begin issuance
showIssuanceUI()
})
})
.catch(displayError)
} else {
app.innerHTML = 'Logging in...'
msalInstance.loginRedirect({ scopes: ['openid', 'profile', 'email'] })
}
})
}
addEventListener('load', initializeMsal)
</script>
</body>
</html>