Receiving Mobile Credentials in Wallet SDK using OpenID Credential Provisioning

Initialise a wallet instance

@mattrglobal/wallet-sdk-react-native does not come in mobile credential support by default. To enable the mobile credential feature, an additional peer dependency @mattrglobal/mobile-credential-holder-react-native is needed. Install the following dependency in your app.

NOTE: mobile credential presentation utilise Bluetooth connection. For iOS make sure the NSBluetoothAlwaysUsageDescription and NSBluetoothPeripheralUsageDescription description are configured inside Info.plist.

typescript
Copy to clipboard.
1yarn add @mattrglobal/mobile-credential-holder-react-native

Include the mobile credential holder extension during initialisation

typescript
Copy to clipboard.
1import { initialise } from "@mattrglobal/wallet-sdk-react-native";
2import MobileCredentialHolder from "@mattrglobal/mobile-credential-holder-react-native";
3
4await initialise({ extensions: [MobileCredentialHolder], walletId });

Start an OpenID Credential Provisioning flow 

Discover OpenID credential offer 

typescript
Copy to clipboard.
1const uri =
2  "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22
3  %3A%22https%3A%2F%2Fmyissuer.example.com%22%2C%22credentials%22%3A%5B%
4  22707e920a-f342-443b-ae24-6946b7b5033e%22%5D%2C%22request_parameters%22
5  %3A%7B%22login_hint%22%3A%22test.user%40example.com%22%2C%22prompt%22%
6  3A%22login%22%7D%7D";
7
8const discoveryResult = await wallet.openid.issuance.discover(uri);
9if (discoveryResult.isErr()) {
10  // Handle error from discoveryResult.error
11}
12const { offer } = discoveryResult.value;

or Construct the offer manually

typescript
Copy to clipboard.
1const offer: OpenidIssuanceCredentialOffer = {
2    issuer: "https://example.com/",
3    authorizeEndpoint: "https://example.com/oauth/authorize",
4    tokenEndpoint: "https://example.com/oauth/token",
5    credentialEndpoint: "https://example.com/oauth/credential",
6    credentials: [
7        {
8            profile: "mobile",
9            scope: "mso_mdoc:com.example.employeecredential.1",
10            docType: "com.example.employeecredential.1"
11        }
12    ]
13}

Generate an OAuth authorization url to request access token to retrieve the credentials

typescript
Copy to clipboard.
1import { Linking } from "react-native";
2
3const generateAuthorizeUrlResult = await wallet.openid.issuance.generateAuthorizeUrl({ offer, clientId, redirectUri });
4
5if (generateAuthorizeUrlResult.isErr()) {
6    // Handle error from generateAuthorizeUrlResult.error
7    return;
8}
9
10const { url, codeVerifier } = generateAuthorizeUrlResult.value;
11await Linking.openURL(url);

Retrieve the token

typescript
Copy to clipboard.
1const retrieveTokenResult = await wallet.openid.issuance.retrieveToken({
2    offer,
3    clientId,
4    redirectUri,
5    codeVerifier,
6    code: route.params.code, // code comes authorization success callback above
7});
8
9if (retrieveTokenResult.isErr()) {
10    // Handle error from retrieveTokenResult.error
11    return;
12}
13
14const { accessToken } = retrieveTokenResult.value;

Retrieve credentials

typescript
Copy to clipboard.
1import { CredentialProfileSupported } from "@mattrglobal/wallet-sdk-react-native";
2const retrieveCredentialsResult = await wallet.openid.issuance.retrieveCredentials({
3  offer, // offer that contains mobile credentials
4  accessToken,
5  clientId,
6});
7
8retrieveCredentialsResult.forEach((credentialOfferResult) => {
9  if ("error" in credentialOfferResult) {
10    const { offer, error } = credentialOfferResult;
11    // Handle error from retrieveCredentialsResult.error
12  } else {
13    const { offer, result } = credentialOfferResult;
14    if (result.profile === CredentialProfileSupported.Mobile) {
15      const credentialId = result.credentialId;
16    }
17  }
18});

Manage Storage of Mobile Credentials

The SDK manages the storage of any retrieved mobile credential. They will be accessible via the following mobile credential functions.

typescript
Copy to clipboard.
1// Get a mobile credential
2const getCredentialResult = await wallet.credential.mobile.getCredential(credentialId);
3
4// Get all mobile credentials
5const getCredentialsResult = await wallet.credential.mobile.getCredentials();
6
7// Delete a mobile credential
8await wallet.credential.mobile.deleteCredential(credentialId);

Manage Trusted Issuer Certificates

The SDK only accept a mobile credential that can be verified successfully by the trusted issuer certificates. The trusted issuer certificates list can be managed directly with the following functions

typescript
Copy to clipboard.
1// Add new trusted issuer certificates
2await wallet.credential.mobile.addTrustedIssuerCertificates(certificates);
3
4// Get all trusted certificates
5const certificates = await wallet.credential.mobile.getTrustedIssuerCertificates();
6
7// Rmove one of the trusted issuer certificate
8await wallet.credential.mobile.deleteTrustedIssuerCertificate(id);

Enable Auto Trust for Issuer Certificates

You may also instruct the SDK to automatically download and add certificates if it's discoverable via the openid credential issuer metadata into the trusted list while retrieving a mobile credential via OpenID4VCI.

typescript
Copy to clipboard.
1const retrieveCredentialsResult = await wallet.openid.issuance.retrieveCredentials({
2  offer, // offer that contains mobile credentials
3  accessToken,
4  clientId,
5  autoTrustMobileCredentialIaca: true, // enable auto trust
6});

Error handling

Functions that are expected to have an error path return a Neverthrow Result type that represents either success (Ok) or failure (Err).

Although this pattern is more verbose, it encourages the handling of possibly errors and reserves throwing exceptions for truly exceptional situations.

Copy to clipboard.
import { initialise } from "@mattrglobal/wallet-sdk-react-native";

const initialiseWalletResult = await initialise();

if (initialiseWalletResult.isErr()) {
  // Handle error from initialiseWalletResult.error
  return;
}

const wallet = initialiseWalletResult.value;

Unwrap

A utility function is provided for convenience if you decide not to handle your errors as results. This function will simply throw an error if the function passed in returns a Result where Result.isErr() is true.

Copy to clipboard.
import { unwrap } from "@mattrglobal/wallet-sdk-react-native";

try {
  const wallet = unwrap(await initialise());
} catch (error) {
  // Handle thrown error
}

Find the comprehensive SDK interfaces for these examples and others in our Wallet SDK Docs.

Get in touch if you wish to find out more about using the Wallet SDK in production.