Learn how to build an Android application that can present an mDoc via a proximity workflow
Introduction
In this tutorial we will use the Android native mDoc Holder SDK to build an Android application that can present a claimed mDoc to a verifier that supports proximity verification as per ISO/IEC 18013-5:2021.
- The user launches the application and generates a QR code.
- The verifier scans the QR code, connects with the application and requests an mDoc for verification.
- The applications displays matching credentials to the user and asks for consent to share them with the verifier.
- The verifier receives the application’s response and verifies the provided credential.
The result will look something like this:
Prerequisites
Before we get started, let’s make sure you have everything you need.
Prior knowledge
-
The verification workflow described in this tutorial is based on the ISO/IEC 18013-5:2021 standard. If you are unfamiliar with this standard, refer to our Docs section for more information:
- What are mDocs?
- What is credential verification?
- Breakdown of the proximity presentation workflow.
-
We assume you have experience developing Android apps in Kotlin.
If you need to get a holding solution up and running quickly with minimal development resources and in-house domain expertise, talk to us about our white-label MATTR GO Hold app which might be a good fit for you.
Assets
As part of your onboarding process you should have been provided with access to the following assets:
- ZIP file which includes the required library: (
holder-*version*.zip
). - Sample Wallet app: You can use this app for reference as we work through this tutorial.
Development environment
Prerequisite tutorial
- You must complete the Claim a credential tutorial, build the application and use it to claim the mDoc provided in the tutorial.
- This application is used as the base for the current tutorial.
Testing devices
As this tutorial implements a proximity presentation workflow, you will need two separate physical mobile devices to test the end-to-end result:
- Holder device:
- Supported Android device to run the built application on, setup with:
- Biometric authentication (Face recognition, fingerprint recognition).
- Bluetooth access and Bluetooth turned on.
- Available internet connection.
- USB debugging enabled.
- Supported Android device to run the built application on, setup with:
- Verifier device:
- Android/iOS mobile device with an installed verifier application. We recommend downloading and using the MATTR GO Verify example app.
- Setup with Bluetooth access.
Got everything? Let’s get going!
Tutorial steps
To enable a user to present a stored mDoc to a verifier via a proximity presentation workflow, you will build the following capabilities into your application:
- Create and present a QR code to establish a secure connection with the verifier application.
- Receive and handle a presentation request from the verifier.
- Send a matching mDoc presentation to the verifier.
Create and Present a QR code to establish a secure connection
The first capability we need to build in is for your application to be able to establish a secure communication channel between the verifier and holder devices. As defined in ISO/IEC 18130-5:2021, a proximity presentation workflow is always initiated by the holder (wallet user), who must create a QR code for the verifier to scan in order to initiate the device engagement phase.
To achieve this, we will first create a new screen where the user can present a QR code with the information required to establish a secure communication channel.
-
In your Claim a tutorial application project, create a new file named
PresentationQrScreen.kt
and add the following code:PresentationQrScreen.ktimport android.app.Activity import android.graphics.Bitmap import android.graphics.Color import android.widget.Toast import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.IntSize import androidx.navigation.NavController import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import com.journeyapps.barcodescanner.BarcodeEncoder import global.mattr.mobilecredential.holder.MobileCredentialHolder import global.mattr.mobilecredential.holder.ProximityPresentationSession import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @Composable fun PresentationQrScreen(activity: Activity, navController: NavController) { var containerSize by remember { mutableStateOf(IntSize.Zero) } var session: ProximityPresentationSession? by remember { mutableStateOf(null) } var qrCode: Bitmap? by remember { mutableStateOf(null) } LaunchedEffect(Unit) { // Step 1.4: Create a session } LaunchedEffect(session, containerSize) { // Step 1.6: Generate a QR code } Box( modifier = Modifier .aspectRatio(1f) .onSizeChanged { containerSize = it } ) { qrCode?.let { Image( bitmap = it.asImageBitmap(), contentDescription = "A QR Code", modifier = Modifier.fillMaxSize() ) } } } // Step 1.5: Create function to generate QR Code from String
We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step.
We recommend copying and pasting the comment text (e.g. // Proximity Presentation - Step 1.2: Add "QR" screen call
) to
easily locate it in the code.
-
Return to your
MainActivity
file and add the following code under the// Proximity Presentation - Step 1.2: Add "QR Presentation" screen call
comment to connect the created composable to the navigation graph:MainActivity.ktPresentationQrScreen(this@MainActivity, navController)
-
Still in the
MainActivity
file file, add the following code under the// Proximity Presentation - Step 1.3: Add button for starting the credentials presentation workflow
comment to add a button that navigates the user to thePresentationQrScreen
:MainActivity.ktButton(onClick = { navController.navigate("presentationQr") }, Modifier.fillMaxWidth()) { Text("Present Credentials") }
-
Open the
PresentationQrScreen.kt
file and add the following code under the// Step: 1.4 Create a session
comment to call thecreateProximityPresentationSession
function when the screen is loaded:PresentationQrScreen.ktsession = MobileCredentialHolder.getInstance().createProximityPresentationSession( activity, onRequestReceived = { _, requests, e -> // Step 2.2: Handle the presentation request } )
Now, when the PresentationQrScreen
is loaded, the application will call the SDK’s
createProximityPresentationSession
function, which returns a
ProximityPresentationSession
instance that includes a deviceEngagement
string in base64
format:
"mdoc:owBjMS4wAYIB2BhYS6QBAiABIVgghaBYJe7KSqcEolhmnIJaYJ2AIevkKbEy5xP7tkwlqAwiWCAMGCGe6uFI2hKeghb59h_K4hPV-Ldq6vnaxsRiySMH9gKBgwIBowD0AfULUKRoj0ZH60Qco-m0k97qRSQ"
This deviceEngagement
string is always prefixed with mdoc:
and contains the information required
to establish a secure connection between the holder and verifier devices, including:
- Wireless communication protocols supported by the holder.
- How the verifier can connect with the holder.
- Ephemeral public key which is unique to the current session.
- Additional feature negotiations.
Your application needs to convert this deviceEngagement
string into a QR code and display it in
the PresentationQrScreen
for the verifier to scan.
-
Add the following code under the
// Step 1.5: Create function to generate QR Code from String
comment to add a function that converts aString
to a QR code rendered as aBitmap
image:PresentationQrScreen.ktprivate fun String.toQrCode(size: IntSize): Bitmap? { if (this.isEmpty() || size == IntSize.Zero) return null val (width, height) = size return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565).apply { val hints = mapOf(EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.M) val encoded = BarcodeEncoder() .encode(this@toQrCode, BarcodeFormat.QR_CODE, width, height, hints) for (x in 0 until width) { for (y in 0 until height) { setPixel(x, y, if (encoded[x, y]) Color.BLACK else Color.WHITE) } } } }
To generate the QR code, we need the ProximityPresentationSession
to be established by the SDK and
the container size to be calculated.
-
Add the following code under the
// Step 1.6: Generate a QR code
comment to call the newtoQrCode
function and generate the QR code when thesession
orcontainerSize
state changes:PresentationQrScreen.ktqrCode = session?.deviceEngagement?.toQrCode(containerSize)
-
Run the app, and select the Present Credentials button. You should see a result similar to the following:
- As the user selects the Present Credentials button, they are navigated to the new
PresentationQrScreen
, and the session is being created. - Once the session is created, the application generates and displays a QR code that can be scanned by a verifier device to establish a secure proximity communication channel over Bluetooth.
Once the QR code is displayed, the
ProximityPresentationSession
enters a listening state, ready to establish a Bluetooth connection with a verifier application that
scans the QR Code.
When a verifier application scans the QR code, the devices will automatically exchange public keys to establish a secure communication channel, enabling the verifier to send a presentation request, which details what information is required and for what purpose.
Handle the presentation request
The
createProximityPresentationSession
function can handle three types of events:
onConnected
: When a secure connection is established.onSessionTerminated
: When a secure connection is terminated for whatever reason.onRequestReceived
: When a presentation request is received from the verifier.
onConnected
and onSessionTerminated
are optional events and will not be implemented in this
tutorial. Check out our SDK
Docs
for a complete description of these events and how you can handle them.
When the SDK receives a presentation request from a verifier, an onRequestReceived
event is
triggered. The SDK then checks its credential storage for any credentials that match the information
defined in this request.
The application then needs to:
- Present these matching credentials to the user.
- Present what claims will be shared with the verifier.
- Provide a UI element for the user to consent sharing this information with the verifier.
-
In your
MainActivity
file, add the following code under the// Step 2.1: Add proximity presentation request variable
comment to create a new variable that will hold the presentation request information and share it between the screens:MainActivity.ktvar proximityPresentationRequest: ProximityPresentationSession.CredentialRequestInfo? = null
-
Open the
PresentationQrScreen.kt
file and add the following code under the// Step 2.2: Handle the presentation request
comment to navigate to a new screen when anonRequestReceived
event is received.PresentationQrScreen.ktif (e == null && !requests.isNullOrEmpty()) { // Using only the first request for simplicity SharedData.proximityPresentationRequest = requests.first() withContext(Dispatchers.Main) { navController.navigate("presentationSelectCredentials") } } else { val msg = "Error while retrieving the request" withContext(Dispatchers.Main) { Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() } }
-
Create a new file named
PresentationSelectCredentialsScreen.kt
and add the following code to create the new screen which displays matching credentials and enables the user to select which credential to share:PresentationSelectCredentialsScreen.ktimport android.app.Activity import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import global.mattr.mobilecredential.common.deviceretrieval.devicerequest.DataElements import global.mattr.mobilecredential.common.deviceretrieval.deviceresponse.NameSpace import global.mattr.mobilecredential.common.dto.MobileCredential import global.mattr.mobilecredential.holder.MobileCredentialHolder import global.mattr.mobilecredential.holder.ProximityPresentationSession import kotlinx.coroutines.launch @Composable fun PresentationSelectCredentialsScreen(activity: Activity) { val request = SharedData.proximityPresentationRequest ?: return if (request.matchedCredentials.isEmpty()) return var presentationRequest by remember { mutableStateOf(request) } var selectedCredentialId by remember { mutableStateOf(presentationRequest.matchedCredentials.first().id) } val coroutineScope = rememberCoroutineScope() Column(Modifier.verticalScroll(rememberScrollState())) { Text("REQUESTED INFORMATION", style = MaterialTheme.typography.titleLarge) Document( presentationRequest.request.docType, presentationRequest.request.nameSpaces.value.toUi() ) Spacer(Modifier.padding(12.dp)) Text("MATCHING CREDENTIALS", style = MaterialTheme.typography.titleLarge) presentationRequest.matchedCredentials.forEach { matchedCredential -> // Step 2.5: Display matching credentials and claims } // Step 3.2: Send response } } // Step 2.4: Create function to add values to claims private fun Map<NameSpace, DataElements>.toUi() = mapValues { (_, dataElements) -> dataElements.value.keys.toSet() } // Step 3.1: Create function to send the credential response
The application retrieves information from the SDK’s ProximityPresentationSession
object and
displays it to the user:
- Credentials and claims included in the verification request are retrieved from the
docType
andnameSpaces.value
properties and displayed in the “REQUESTED INFORMATION” area. - Available credentials that match the requested information are retrieved from the
matchedCredentials
property and displayed in the “MATCHING CREDENTIALS” area.
-
Add the following code under the
// Step 2.4: Create function to add values to claims
comment to create a new function that will enable displaying the values of the claims the user is about to share.PresentationSelectCredentialsScreen.ktprivate fun ProximityPresentationSession.CredentialRequestInfo.withClaimValues( credentialWithValues: MobileCredential ): ProximityPresentationSession.CredentialRequestInfo = copy( matchedCredentials = matchedCredentials.map { credential -> if (credential.id == credentialWithValues.id) { credential.copy( claims = credential.claims.mapValues { (namespace, claims) -> claims.map { claim -> val claimValue = credentialWithValues.claims[namespace]?.get(claim) claimValue?.let { "$claim: ${it.toUiString()}" } ?: claim }.toSet() } ) } else { credential } } )
-
Add the following code under the
// Step 2.5: Display matching credentials and claims
comment to display to the user what credentials and claims they are about to share with the verifier, as well as a button that enables the user to display the value of those claims as well (we do not show this by default as claims are likely to include PII):PresentationSelectCredentialsScreen.ktRow( Modifier .fillMaxWidth() .clickable { selectedCredentialId = matchedCredential.id }) { Column { RadioButton( selected = matchedCredential.id == selectedCredentialId, onClick = { selectedCredentialId = matchedCredential.id } ) TextButton( onClick = { val credentialWithValues = MobileCredentialHolder.getInstance() .getCredential(matchedCredential.id, skipStatusCheck = true) presentationRequest = presentationRequest.withClaimValues(credentialWithValues) } ) { Text("Show\nValues") } } Document( matchedCredential.docType, matchedCredential.claims, Modifier.weight(1f) ) }
-
Back in the
MainActivity
file, add the following code under the// Proximity Presentation - Step 2.6: Add "Select Credential" screen call
comment to connect the created composable to the navigation graph:MainActivity.ktPresentationSelectCredentialsScreen(this@MainActivity)
-
Run the app, and select the Present Credentials button. Next, use your testing verifier app to scan the presented QR code and send a presentation request. You should see a result similar to the following:
- As the user selects the Present Credentials button, they are navigated to the new
PresentCredentialsView
where the application generates and displays a QR code. - When a compliant verifier app scans the QR code, a secure communication channel is established via Bluetooth.
- The verifier then sends a presentation request, which is displayed to the user alongside any credentials they have stored that match the request.
Send a presentation response
The next capability we need to build is for the application to send a presentation response upon receiving consent from the holder to share information with the verifier.
Once the user provides the consent by selecting the document they want to share, the application
should call the SDK’s
sendResponse
function to share the information with the verifier as a presentation response.
-
Open the
PresentationSelectCredentialsScreen.kt
file and add the following code under the// Step 3.1: Create function to send the credential response
comment to create a new function that calls the SDK’ssendResponse
method.PresentationSelectCredentialsScreen.ktprivate suspend fun sendResponse(credentialId: String, activity: Activity) { MobileCredentialHolder.getInstance() .getCurrentProximityPresentationSession() ?.sendResponse(listOf(credentialId), activity) }
The
sendResponse
function signs the presentation response with the user’s device private key (to prove Device authentication) and shares it as an encoded CBOR file. -
Add the following code under the
// Step 3.2: Send response
to call the createdsendResponse
function when the user selects the “Send Response” button:PresentationSelectCredentialsScreen.ktButton( onClick = { coroutineScope.launch { sendResponse(selectedCredentialId, activity) } }, Modifier.fillMaxWidth() ) { Text("Send Response") }
-
Run the app and perform the following:
- Select the Present Credentials button.
- Use your testing verifier app to scan the present QR code and send a presentation request.
- Back on the holder device, select the matching credential to share.
- Select Send Response.
You should see a result similar to the following:
As the user selects the credential to share, the verifier app will receive the presentation response, verify any incoming credentials and display the verification results.
Congratulations, you have now completed this tutorial, and should have a working application that can claim an mDoc using an OID4VCI workflow, and present it to a verifier for proximity verification via Bluetooth.
Summary
You have just used the Android mDoc holder SDK to built an application that can present a claimed mDoc to a verifier that supports proximity verification as per ISO/IEC 18013-5:2021.
This was achieved by building the following capabilities into the application:
- Generating a QR code for the verifier to scan and establish a secure communication channel.
- Receive and handle a presentation request from the verifier.
- Display matching credentials to the user and ask for consent to share them with the verifier.
- Send matching credentials to the verifier as a presentation response.
What’s next?
- You can check out the Android mDoc holder SDK Docs to learn more about available functions and classes.