Skip to main content
Version: v0.5.x

Receiving and Proving Credentials using the OpenID4VC Holder Module

This tutorial will guide you through the process of receiving and proving credentials using the OpenID4VC Holder Module. Before starting this tutorial, make sure you have completed the OpenID4VC Holder Module Setup.

This guides only covers the receiving and proving of credentials using the OpenID4VC Holder Module. Follow the Issuing Credentials using the OpenID4VC Issuer Module and Verifying Credentials using the OpenID4VC Verifier Module guides to learn how to issue and verify credentials using the OpenID4VC Issuer and Verifier Modules.

Resolving and accepting a credential offer

Once you have set-up your agent (under holder variable), and have a credential offer (either created using the issuer module, or an external OpenID4VC issuer), we can resolve and accept the credential offer.

The credentialBindingResolver is a method you need to provide that configures how the credential should be bound to the wallet. The implemented binding resolver in this tutorial first checks if the issuer supports did:key and will use that. Otherwise it will check if jwk is supported.

import { KeyDidCreateOptions, getJwkFromKey, DidKey } from '@credo-ts/core'

// resolved credential offer contains the offer, metadata, etc..
const resolvedCredentialOffer = await holder.modules.openId4VcHolderModule.resolveCredentialOffer(credentialOffer)
console.log('Resolved credential offer', JSON.stringify(resolvedCredentialOffer.credentialOfferPayload, null, 2))

// issuer only supports pre-authorized flow for now
const credentials = await holder.modules.openId4VcHolderModule.acceptCredentialOfferUsingPreAuthorizedCode(
resolvedCredentialOffer,
{
credentialBindingResolver: async ({
supportedDidMethods,
keyType,
supportsAllDidMethods,
// supportsJwk now also passed
supportsJwk,
credentialFormat,
}) => {
// NOTE: example implementation. Adjust based on your needs
// Return the binding to the credential that should be used. Either did or jwk is supported

if (supportsAllDidMethods || supportedDidMethods?.includes('did:key')) {
const didResult = await holder.dids.create<KeyDidCreateOptions>({
method: 'key',
options: {
keyType,
},
})

if (didResult.didState.state !== 'finished') {
throw new Error('DID creation failed.')
}

const didKey = DidKey.fromDid(didResult.didState.did)

return {
method: 'did',
didUrl: `${didKey.did}#${didKey.key.fingerprint}`,
}
}

// we also support plain jwk for sd-jwt only
if (supportsJwk && credentialFormat === OpenId4VciCredentialFormatProfile.SdJwtVc) {
const key = await holder.wallet.createKey({
keyType,
})

// you now need to return an object instead of VerificationMethod instance
// and method 'did' or 'jwk'
return {
method: 'jwk',
jwk: getJwkFromKey(key),
}
}

throw new Error('Unable to create a key binding')
},
}
)

console.log('Received credentials', JSON.stringify(credentials, null, 2))

// Store the received credentials
const records: Array<W3cCredentialRecord | SdJwtVcRecord> = []
for (const credential of credentials) {
if ('compact' in credential) {
const record = await holder.sdJwtVc.store(credential.compact)
records.push(record)
} else {
const record = await holder.w3cCredentials.storeCredential({
credential,
})
records.push(record)
}
}

Finally the credentials are stored using the SD JWT VC and W3C modules. In a wallet application you could choose to first show the credential to the user before storing it in the wallet.

Resolving and accepting an authorization request (presentation request)

Once you have a credential in your wallet, you can start presenting it based on a receive authorization request including an OpenID4VP presentation request (either created using the verifier module, or an external OpenID4VC verifier). First we resolve the authorization request, and then we accept it and present the credential in our wallet.


// resolved credential offer contains the offer, metadata, etc..
const resolvedAuthorizationRequest = await holder.modules.openId4VcHolderModule.resolveSiopAuthorizationRequest(
authorizationRequest
)
console.log(
'Resolved credentials for request',
JSON.stringify(resolvedAuthorizationRequest.presentationExchange.credentialsForRequest, null, 2)
)

const presentationExchangeService = holder.dependencyManager.resolve(DifPresentationExchangeService)
// Automatically select credentials. In a wallet you could manually choose which credentials to return based on the "resolvedAuthorizationRequest.presentationExchange.credentialsForRequest" value
const selectedCredentials = presentationExchangeService.selectCredentialsForRequest(
resolvedAuthorizationRequest.presentationExchange.credentialsForRequest
)

// issuer only supports pre-authorized flow for now
const authorizationResponse = await holder.modules.openId4VcHolderModule.acceptSiopAuthorizationRequest({
authorizationRequest: resolvedAuthorizationRequest.authorizationRequest,
presentationExchange: {
credentials: selectedCredentials,
},
})
console.log('Submitted authorization response', JSON.stringify(authorizationResponse.submittedResponse, null, 2))