Getting started | Contributing | License
An Expo Module to automatically set up and configure Digital Credentials API for Android in Expo apps.
- Currently two default matcher implementations for matching credentials based on a request is bundled, which only supports mdoc, dc+sd-jwt, openid4vp , dcql, signed requests and unsigned requests. In the future support for a custom matcher might be added.
- During development when the activity is launched and the application is already running this results in render errors. In production these errors won't occur, but it does hinder the development experience. We're still looking for a solution.
- This library is tested with Expo 52 and React Native 0.76. It uses some hacks to use Kotlin 2.0.21, and is likely to break in non-default application setups. React Native 77 will use Kotlin 2 by default, and these hacks shouldn't be needed anymore.
- When using the CMWallet matcher, icons provided for credentials are not rendered.
Note
This library integrates with experimental Android APIs, as well as draft versions of several specifications. Expect all APIs to break in future releases.
Install the module using the following command.
# yarn
yarn add @animo-id/expo-digital-credentials-api
# npm
npm install @animo-id/expo-digital-credentials-api
# npm
pnpm install @animo-id/expo-digital-credentials-api
Then prebuild the application so the Expo Module wrapper can be added as native dependency (If you aren't making any manual modification to the Android directories you can add them to the gitignore of your project and generate them on demand):
# yarn
yarn expo prebuild
# npm
npx expo prebuild
That's it, you now have the Digital Credentials API configured for your Android project.
Warning
You might need to set the Kotlin version of your project to 2.0.21. To do this, add the expo-build-properties
dependency to your project, and configure it with android.kotlinVersion
set to '2.0.21'
.
[
"expo-build-properties",
{
"android": {
"kotlinVersion": "2.0.21"
}
}
]
You can now import @animo-id/expo-digital-credentials-api
in your application.
To make Android aware of the credentials availble in your wallet, you need to register the credentials. Every time the credentials in your application changes, you should call this method again.
When registering credentials you can also choose the matcher that is used. When registering credentials with a new matcher the old matcher will not be used anymore (the latest register call always overrides previous calls). The supported matchers are:
- CMWallet matcher taken from https://github.com/digitalcredentialsdev/CMWallet. (current version)
- Supports SD-JWT VC and mDOC
- Supports signed requests
- Does not support icons
- Does not support showing claim values
- Ubique matcher taken from https://github.com/UbiqueInnovkation/oid4vp-wasm-matcher. (current version).
- Supports SD-JWT VC and mDOC
- Supports signed requests
- Supports icons
- Supports showing claim values
import {
registerCredentials,
RegisterCredentialsOptions,
} from "@animo-id/expo-digital-credentials-api";
// See RegisterCredentialsOptions for all options
await registerCredentials({
matcher: "cmwallet",
credentials: [
{
id: "1",
display: {
title: "Drivers License",
subtitle: "Issued by Utopia",
claims: [
{
path: ["org.iso.18013.5.1", "family_name"],
displayName: "Family Name",
},
],
iconDataUrl:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAaUlEQVR4nOzPUQkAIQDA0OMwpxksY19D+PEQ9hJsY6/5vezXAbca0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0E4AAAD//7vSAeZjAN7dAAAAAElFTkSuQmCC",
},
credential: {
doctype: "org.iso.18013.5.1.mDL",
format: "mso_mdoc",
namespaces: {
"org.iso.18013.5.1": {
family_name: "Glastra",
},
},
},
},
{
id: "2",
display: {
title: "PID",
subtitle: "Issued by Utopia",
claims: [
{
path: ["first_name"],
displayName: "First Name",
},
{
path: ["address", "city"],
displayName: "Resident City",
},
],
iconDataUrl:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAaUlEQVR4nOzPUQkAIQDA0OMwpxksY19D+PEQ9hJsY6/5vezXAbca0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0E4AAAD//7vSAeZjAN7dAAAAAElFTkSuQmCC",
},
credential: {
vct: "eu.europa.ec.eudi.pid.1",
format: "dc+sd-jwt",
claims: {
first_name: "Timo",
address: {
city: "Somewhere",
},
},
},
},
],
} satisfies RegisterCredentialsOptions);
When the user has selected a credential from your application, the application will be launched with an intent to retrieve the credentials. A custom component will be used and rendered as an overlay.
You should register the component as early as possible, usually in your index.ts
file. If you're using Expo Router, follow these steps to setup a custom entry point.
The component will be rendered in a full screen window, but with a transparent background. This allows you to render an overlay rather than a full screen application. By default all screen content that you do not render something over, has an onPress
handler and will abort the request. You can disable this by setting cancelOnPressBackground
to false
.
import { registerRootComponent } from "expo";
import App from "./App";
import { MyCustomComponent } from "./MyCustomComponent";
// import the component registration method
// make sure to import this from the /register path
// so it doesn't load the native module yet, as that will prevent the app from correctly loading
import registerGetCredentialComponent from "@animo-id/expo-digital-credentials-api/register";
// Registers the componetn to be used for sharing credentials
registerGetCredentialComponent(MyCustomComponent);
// Default expo method call
registerRootComponent(App);
The request is passed to the registered component as request
and has type DigitalCredentialsRequest
.
import {
type DigitalCredentialsRequest,
sendErrorResponse,
sendResponse,
} from "@animo-id/expo-digital-credentials-api";
import { Button } from "react-native";
import { Text, View } from "react-native";
export function MyCustomComponent({
request,
}: {
request: DigitalCredentialsRequest;
}) {
return (
<View style={{ width: "100%" }}>
<Button
title="Send Response"
onPress={() =>
sendResponse({ response: JSON.stringify({ vp_token: "something" }) })
}
/>
<Button
title="Send Error Response"
onPress={() =>
sendErrorResponse({ errorMessage: "Send error response" })
}
/>
</View>
);
}
If you're using Expo Router, the root application is automatically loaded and executed, even if a custom activity is launched in React Native, and thus your main application logic will be executed (although not visible).
To prevent this from happening, you can create a small wrapper that returns null
when the current activity is the get credential activitiy using the isGetCredentialActivity
method. Make sure to only call this method once your app component is loaded, to prevent the app loading to get stuck.
import { isGetCredentialActivity } from "@animo-id/expo-digital-credentials-api";
export default function App() {
const isDcApi = useMemo(() => isGetCredentialActivity(), []);
if (isDcApi) return null;
return <MainApp />;
}
Is there something you'd like to fix or add? Great, we love community contributions! To get involved, please follow our contribution guidelines.
Expo Digital Credentials Api is licensed under the Apache 2.0 license.