CHAPI for Native Wallet
Resources
- Example Code: the chapi-demo-wallet contains a full example implementation and is referenced throughout this guide.
- Polyfill Library: The credential-handler-polyfill library provides the needed
CredentialHandler
API within the browser. - Helper Library: The web-credential-handler library provides helper functions for CHAPI integration in your code.
Native Wallet Registration
All Wallets need to be registered with the browser as a Credential Handler in order to store or retrieve Verifiable Credentials (VCs) on the Web.
To enable a native mobile wallet to receive VCs via CHAPI, your application will need to be configured to receive deep links from the mobile OS.
Deep links, also known as app links, cause a user's Web browser to open a native application and pass any URL to it when particular links are followed. This enables the user's Web browser to open a native mobile wallet from the CHAPI wallet selection menu and pass the CHAPI request to it. See appropriate deep link documentation for Android or iOS.
Consequently, you will be required by the mobile OS to host the appropriate operating system specific files on a Web server.
In addition, to enable CHAPI registration, your Web server will also need to host a manifest.json
file as well as a registration page that allows the user to register your wallet with CHAPI within their mobile Web browser.
1. Add a credential_handler
to the server's manifest.json
In order to register as a Credential Handler, your registration page's server must serve a manifest.json
file from its root path (/manifest.json
). This endpoint must be CORS-enabled which can be done by adding the Access-Control-Allow-Origin: *
header to responses for that URL.
Now, add the following credential_handler
object with the appropriate acceptedProtocols
your wallet can handle:
{
"credential_handler": {
"url": "/switchboard",
"enabledTypes": ["VerifiablePresentation"],
"acceptedInput": "url",
"acceptedProtocols": [
"OID4VCI",
"OID4VP",
"vcapi"
]
}
}
The url
property must be the entry point for your mobile wallet so it must be a deep link that will open the application. Details about the event that initiates the opening of this URL will be relayed via query parameters appended to it.
The "acceptedInput": "url"
line tells CHAPI that the wallet should receive data via opening the URL and providing query parameters rather than browser sent events typically used with Web wallets.
2. Allow your users to register their wallet as a Credential Handler with the browser polyfill
To register with CHAPI, your user must trigger the registration of your wallet as a Credential Handler within their browser. This must be initiated by user interaction, so you cannot automatically register without the user clicking or tapping a button or link.
We will load two libraries into the Wallet code to enable the registration:
- Polyfill Library: The credential-handler-polyfill library provides the needed
CredentialHandler
API within the browser. - Helper Library: The web-credential-handler library provides helper functions for CHAPI integration in your code.
You can add these libraries via <script>
tags and watch a click event to
trigger the CHAPI registration and handler installation:
<button id="installHandlerButton">Register Wallet</button>
<script src="https://unpkg.com/credential-handler-polyfill@3/dist/credential-handler-polyfill.min.js"></script>
<script src="https://unpkg.com/web-credential-handler@2/dist/web-credential-handler.min.js"></script>
<script>
document.getElementById('installHandlerButton').addEventListener('click', async function() {
try {
await credentialHandlerPolyfill.loadOnce();
await WebCredentialHandler.installHandler();
console.log('Handler Installed Successfully!');
} catch (error) {
console.error('Error installing handler: ' + error.message);
}
});
</script>
3. Link users to the registration page from your application
Your mobile application should include a link to your registration page. That link should open the device's default browser and allow them to register the wallet as a Credential Handler.
Once done, your Wallet should now be enabled to handle various credential related requests from across the Web.
Verifiable Credential Storage
Now that your native wallet has been registered with CHAPI, it can receive requests to store credentials at the URL stated in the credential_handler.url
of the manifest.json
above.
CHAPI will use the endpoint declared (/switchboard
in the example above) when it provides links to available wallets in the user's browser:
The user will click the provided link to select your wallet from the list.
That link will contain a request
query parameter with a URL encoded JSON object containing the following properties:
credentialRequestOrigin
: This will tell the wallet where the request originatedprotocols
: This will include any available credential exchange protocols (for issuance and/or presentation). The wallet may retrieve the credential for storage from any of the available protocols.
Here is an example URL showing the request
query parameter and its URL encoded value:
https://wallet.example.com/switchboard?request=%7B%22credentialRequestOrigin%22%3A%22https%3A%2F%2Fvcplayground.org%22%2C%22protocols%22%3A%7B%22OID4VCI%22%3A%22openid-credential-offer%3A%2F%2F%3Fcredential_offer%3D%257B%2522credential_issuer%2522%253A%2522https%253A%252F%252Fexample.exchanger.com%252Fexchangers%252Fz1A1GqykGBWKbwhFCDqFjMfnG%252Fexchanges%252Fz1A36rr6wEL25EEiikKvisVEC%2522%252C%2522credentials%2522%253A%255B%257B%2522format%2522%253A%2522ldp_vc%2522%252C%2522credential_definition%2522%253A%257B%2522%2540context%2522%253A%255B%2522https%253A%252F%252Fwww.w3.org%252F2018%252Fcredentials%252Fv1%2522%252C%2522https%253A%252F%252Fpurl.imsglobal.org%252Fspec%252Fob%252Fv3p0%252Fcontext.json%2522%255D%252C%2522type%2522%253A%255B%2522VerifiableCredential%2522%252C%2522OpenBadgeCredential%2522%255D%257D%257D%255D%252C%2522grants%2522%253A%257B%2522urn%253Aietf%253Aparams%253Aoauth%253Agrant-type%253Apre-authorized_code%2522%253A%257B%2522pre-authorized_code%2522%253A%25220065a8a0-069b-46f1-a857-4e1ce5047afd%2522%257D%257D%257D%22%2C%22vcapi%22%3A%22https%3A%2F%2Fexchanger.example.com%2Fexchangers%2Fz1A1GqykGBWKbwhFCDqFjMfnG%2Fexchanges%2Fz19mxa763DAKX7diL51kBFecZ%22%7D%7D
The request
value is URL encoded. Its contents look like this when unencoded:
{
"credentialRequestOrigin": "https://vcplayground.org",
"protocols": {
"vcapi": "https://exchanger.example.com/exchangers/z1A1GqykGBWKbwhFCDqFjMfnG/exchanges/z19mxa763DAKX7diL51kBFecZ",
"OID4VCI": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fexample.exchanger.com%2Fexchangers%2Fz1A1GqykGBWKbwhFCDqFjMfnG%2Fexchanges%2Fz1A36rr6wEL25EEiikKvisVEC%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fpurl.imsglobal.org%2Fspec%2Fob%2Fv3p0%2Fcontext.json%22%5D%2C%22type%22%3A%5B%22VerifiableCredential%22%2C%22OpenBadgeCredential%22%5D%7D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%220065a8a0-069b-46f1-a857-4e1ce5047afd%22%7D%7D%7D"
}
}
There are currently two possible protocols that Issuers may use with CHAPI in order to issue Verifiable Credentials to be stored in the user's digital wallet: vcapi
and OID4VCI
.
Depending on what you declared in your acceptedProtocols
you may receive either or both as properties in the protocols
object carried in the request
query parameter. Your wallet's code (i.e. in /switchboard
) should decide which protocol to use (or it may delegate this choice to advanced users) when multiple protocols are available.
VC-API
The vcapi
property will contain a URL the wallet can use to retrieve the issued credential(s).
To store one or more credentials via VC-API, the wallet first sends a POST
request
to the URL (extracted from protocols.vcapi
above) with an empty JSON object
({}
) in the body of the request to attempt to download the VC to be stored.
POST /exchangers/z1A1GqykGBWKbwhFCDqFjMfnG/exchanges/z19mxa763DAKX7diL51kBFecZ
Host: exchanger.example.com
Content-Type: application/json
{}
The response to that request will be an object that may have a
verifiablePresentation
object and / or a verifiablePresentationRequest
object. If a verifiablePresentation
object is present, it will contain any
verifiable credentials that were issued. If a verifiablePresentationRequest
object is present, then there are further steps required in the exchange
such as DID Auth. The verifiablePresentationRequest
object will describe
what is required to continue the exchange. When no
verifiablePresentationRequest
object is present, the exchange is complete.
Below is a partial example response:
{
"verifiablePresentation": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"type": ["VerifiablePresentation"],
"holder": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"verifiableCredential": [{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.edu/credentials/3732",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
...
}]
}
}
More complete examples can be found in the Example Exchanges section of the VC-API Specification.
OID4VCI
The OID4VCI
property will be a URL that includes all of the required information to complete the issuance of the Verifiable Credential using the OID4VCI protocol.
Example OID4VCI protocol URL:
openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fexchanger.example.com%2Fexchangers%2Fz1A1GqykGBWKbwhFCDqFjMfnG%2Fexchanges%2Fz1A36rr6wEL25EEiikKvisVEC%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fpurl.imsglobal.org%2Fspec%2Fob%2Fv3p0%2Fcontext.json%22%5D%2C%22type%22%3A%5B%22VerifiableCredential%22%2C%22OpenBadgeCredential%22%5D%7D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%220065a8a0-069b-46f1-a857-4e1ce5047afd%22%7D%7D%7D
The credential_offer
query parameter will be URL decoded resulting in a JSON
object containing the following details:
{
"credential_issuer": "https://exchanger.example.com/exchangers/z1A1GqykGBWKbwhFCDqFjMfnG/exchanges/z1A36rr6wEL25EEiikKvisVEC",
"credentials": [
{
"format": "ldp_vc",
"credential_definition": {
"@context": ["https://www.w3.org/2018/credentials/v1", "https://purl.imsglobal.org/spec/ob/v3p0/context.json"],
"type": ["VerifiableCredential","OpenBadgeCredential"]
}
}
],
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "0065a8a0-069b-46f1-a857-4e1ce5047afd"
}
}
}
See the OID4VCI Specification for more details on this protocol.
Verifiable Credential Presentation
Browser side verification flows will open the CHAPI wallet selector in the user's browser. If they select a native wallet, the URL will at the Wallet's pre-registered domain. The app link setup done earlier will cause the URL to open in the user's wallet (rather than the default browser).
Based on our example above, the URL would look similar to this one:
https://wallet.example.com/switchboard?request=
Depending on the protocols supported, the value of the request
query parameter
will contain one or more encoded JSON objects with the applicable details needed
for using that protocol.
Below is a step-by-step sequence diagram of what's taking place:
sequenceDiagram # User visits a Verifier's website and interacts with it to trigger a verification participant site as Verifier Site participant chapi as User's Browser / CHAPI participant app as Native Wallet App participant exchanger as VC-API Exchanger site ->> exchanger: 1. creates an exchange within a workflow exchanger ->> site: 2. JSON response contains the exchange URL/ID site ->> chapi: 3. triggers CHAPI `get()` request w/blank `VerifiablePresentation` object + `protocols.vcapi` containing URL chapi ->> app: 4. offers app URL (w/`?request=`) to open app app ->> exchanger: 5. uses VC-API URL from `?request=` to send VPR exchanger ->> app: 6. returns VPR to wallet app ->> exchanger: 7. responds with VP exchanger ->> app: 9. sends success response to wallet loop poll exchange status site ->> exchanger: exchange complete? exchanger ->> site: ...wait for it... end
VC-API
The vcapi
property within the object parsed above will contain a URL the wallet can use to handle that request.
To respond with zero or more credentials via a VC-API exchanger, the wallet MUST first send a POST
request
to the URL (extracted from protocols.vcapi
above) with an empty JSON object
({}
) in the body of the request to attempt to get a Verifiable Presentation
Request with the details about what the verifier wants.
POST /exchangers/z1A1GqykGBWKbwhFCDqFjMfnG/exchanges/z19mxa763DAKX7diL51kBFecZ
Host: exchanger.example.com
Content-Type: application/json
{}
The response to that request will be a verifiablePresentationRequest
object. The verifiablePresentationRequest
object will describe
what is required to continue the exchange.
Below is an example verifiablePresentationRequest
containing a QueryByExample
query:
{
"verifiablePresentationRequest": {
"query": [
{
"type": "QueryByExample",
"credentialQuery": {
"reason": "Please present your Verifiable Credential to complete the verification process.",
"example": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"type": [
"UniversityDegreeCredential"
]
}
}
}
]
}
}
The wallet should fulfill the request by sending another POST
request to the
same exchange URL with a verifiablePresentation
containing the user selected
credentials:
{
"verifiablePresentation": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"type": ["VerifiablePresentation"],
"holder": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"verifiableCredential": [{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "http://example.edu/credentials/3732",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
...
}]
}
}
When no verifiablePresentationRequest
object is present, the exchange is
complete.
More complete examples can be found in the Example Exchanges section of the VC-API Specification.