Skip to main content

Presentation

This page shows you how to use the JavaScript client to present credentials. For detailed info on credential presentation, refer to the Presentation guide.

Authentication

Applications with an authenticated user should include an identity reference when acquiring presentation tokens. This ensures presentations of platform credentials cannot be made using credentials from another identity and also supports presentation of partner credentials.

Anonymous presentations are also supported, for applications where there is no authenticated user, those applications should use anonymous presentation tokens.

Refer to the Authorization guide for further info. The complete example below uses a anonymous presentation token.

Presentation steps

Presentation is a multi-step process described in the Presentation guide.

1. Acquire an access token

Obtain a limited access token for presentations 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 a presentation token
acquireTokenForPresentation().then((data) => {
// contruct a VerifiedOrchestrationClient with the presentation token
verifiedOrchestrationClient = new verifiedOrchestrationClientJs.VerifiedOrchestrationClient({
url: '<Verified Orchestration API URL>',
accessToken: data.token,
})
// begin presentation
present()
})
})

Or obtain an anonymous presentation token for presentation

async function acquireTokenForAnonymousPresentations() {
app.innerHTML = 'Acquiring token for anonymous presentation...'
const response = await fetch('https://<TODO: your backend API>/acquireTokenForAnonymousPresentations', {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
})
return await response.json()
}

2. Create a presentation request

Create a presentation request specifying the requested credentials and optionally:

  • whether revoked credentials can be presented
  • callback info, if presentation event data should also be sent to a server-side endpoint
const credentialTypes = ['TODO fill this in '] // e.g. ['VerifiedContractor', 'LightRigidLicense', 'MediumRigidLicense']
const allowRevokedConfiguration = {
configuration: {
validation: {
allowRevoked: true,
},
},
}
function present() {
app.innerHTML = 'Generating presentation request...<br>'
verifiedOrchestrationClient
.createPresentationRequest(
{
clientName: 'Test client',
requestedCredentials: credentialTypes.map((credentialType) => ({
type: credentialType,
...allowRevokedConfiguration,
})),
callback: { url: 'https://<your-callback-url>' },
},
onPresentationEvent,
)
.then((response) => onPresentation(response))
.catch(displayError)
}

If the user is on a mobile device, open the presentation link in the browser. Otherwise, display the QR code for the user to scan.

function onPresentation(response) {
if ('error' in response) {
displayError(response.error)
return
}
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}" /><br>`
}
}

5. Receive presentation notification

Upon completion of presentation, the event.requestStatus will be presentation_successful and event.presentation will contain data (see the PresentationEventData type for details).

Example handling presentation events and displaying a subset of data
function onPresentationEvent(data) {
if (data instanceof Error || data.event.requestStatus === 'presentation_error') {
displayError(data ?? { message: 'Presentation failed.' })
} else if (data.event.requestStatus === 'request_retrieved') {
app.innerHTML = 'Scanned, waiting for presentation...'
} else {
// Handle presentation_successful
displayPresentation(data.presentation)
}
}

function displayPresentation(presentation) {
const { presentedCredentials, identity } = presentation
const credentialsWithIssuance = presentedCredentials.map((presentedCredential) => ({
presentedCredential,
issuance: presentation?.issuances.find((issuance) => issuance.contract.credentialTypes.includes(presentedCredential.type[1])),
}))
app.innerHTML = `
<div>Presentation complete ✅</div>
<div>
Presented types:
<ul>
${credentialsWithIssuance
.map(
({
issuance: { contract },
presentedCredential: {
type: [_, credentialType],
},
}) =>
`<li key="${credentialType}">${credentialType} - ${contract.name} <img height="24" src="${contract.display.card.logo.uri}" /></li>`,
)
.join('\n')}
</ul>
</div>
<div>
Presenter identity:
<ul>
<li>name: ${identity.name}</li>
<li>issuer: ${identity.issuer}</li>
<li>identifier: ${identity.identifier}</li>
</ul>
</div>
<button onClick="present()">Present again</button>
`
}

Complete example

Complete working HTML page example
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Present Credentials</title>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@verified-orchestration/client-js/index.min.js"></script>
</head>
<body>
<div id="app">Loading...</div>
<script type="text/javascript">
// The credential types that can be requested by this client
const credentialTypes = ['TODO fill this in '] // e.g. ['VerifiedContractor', 'LightRigidLicense', 'MediumRigidLicense']
// Allowing revoked credentials allows the verifying application to handle revocation status gracefully
const allowRevokedConfiguration = {
configuration: {
validation: {
allowRevoked: true,
},
},
}

const app = document.getElementById('app')
let verifiedOrchestrationClient

function displayError(error) {
app.innerHTML = `Error: ${error.message}`
}

// Create presentation request
function present() {
app.innerHTML = 'Generating presentation request...'
verifiedOrchestrationClient
.createPresentationRequest(
{
clientName: 'Test client',
requestedCredentials: credentialTypes.map((credentialType) => ({
type: credentialType,
...allowRevokedConfiguration,
})),
callback: { url: 'https://<your-callback-url>' },
},
onPresentationEvent,
)
.then((response) => onPresentation(response))
.catch(displayError)
}

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}" />`
}
}

function onPresentationEvent(data) {
if (data instanceof Error || data.event.requestStatus === 'presentation_error') {
displayError(data ?? { message: 'Presentation failed.' })
} else if (data.event.requestStatus === 'request_retrieved') {
app.innerHTML = 'Scanned, waiting for presentation...'
} else {
displayPresentation(data.presentation)
}
}

function onPresentation(response) {
if ('error' in response) {
displayError(response.error)
return
}
app.innerHTML = `<div>Scan to present credentials</div>`
const { qrCode, url } = response
displayQRCodeOrOpenLink(qrCode, url)
}

function displayPresentation(presentation) {
const { presentedCredentials, identity } = presentation
const credentialsWithIssuance = presentedCredentials.map((presentedCredential) => ({
presentedCredential,
issuance: presentation?.issuances.find((issuance) => issuance.contract.credentialTypes.includes(presentedCredential.type[1])),
}))
app.innerHTML = `
<div>Presentation complete ✅</div>
<div>
Presented types:
<ul>
${credentialsWithIssuance
.map(
({
issuance: { contract },
presentedCredential: {
type: [_, credentialType],
},
}) =>
`<li key="${credentialType}">${credentialType} - ${contract.name} <img height="24" src="${contract.display.card.logo.uri}" /></li>`,
)
.join('\n')}
</ul>
</div>
<div>
Presenter identity:
<ul>
<li>name: ${identity.name}</li>
<li>issuer: ${identity.issuer}</li>
<li>identifier: ${identity.identifier}</li>
</ul>
</div>
<button onClick="present()">Present again</button>
`
}

// Acquire an anonymous presentation token from a secure backend API,
// the presentation token allows a fixed set of credential types to be presented by the user, or optionally by anonymous users
async function acquireTokenForAnonymousPresentations() {
app.innerHTML = 'Acquiring token for anonymous presentation...'
const response = await fetch('https://<your-backend-API>/acquireTokenForAnonymousPresentations', {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
})
return await response.json()
}

addEventListener('load', () => {
acquireTokenForAnonymousPresentations()
.then((data) => {
verifiedOrchestrationClient = new verifiedOrchestrationClientJs.VerifiedOrchestrationClient({
url: '<Verified-Orchestration-API-URL>',
accessToken: data.token,
})
present()
})
.catch(displayError)
})
</script>
</body>
</html>