Digital IDs can be accepted both in-app and web flows. To accept credentials from Google Wallet you will need to:
- Integrate using app or web following the provided instructions and
- Fill out this form to request and agree to the terms of service of accepting credentials from Google Wallet.
Prerequisites
To test presentation of IDs digitally, you must first enroll in the public beta program using the intended test account (this needs to be a gmail account). Subsequently, furnish the ensuing details to your designated Google contact.
- Terms of Service link
- Logo
- Website
- App package IDs (for Android app integrations)
- Including dev / debug builds
- App signature
$ $ANDROID_SDK/build-tools/$BUILD_TOOLS_VERSION/apksigner verify --print-certs -v $APK
- Gmail ID that was used to join the public beta
Supported Credential Formats
Several proposed standards exist that define the data format of digital identity documents, with two gaining significant industry traction:
- mdocs - defined by ISO.
- w3c Verifiable Credentials - defined by the w3c.
While the Android Credential Manager supports both the formats, Google Wallet supports only mdoc based Digital IDs at the moment.
Supported Credentials
Google Wallet supports 2 credential types:
- Mobile Drivers License (mDL)
- ID pass
You can request either credential in your flow with a single parameter change.
User experience
When an application requests identity attributes, the following process occurs:
Credential Discovery: The application queries available wallets to identify credentials that can satisfy the request. Android then presents a system UI selector, displaying the information to be shared. This allows the user to make an informed decision about which credential to use.
User Selection and Wallet Interaction: The user selects a credential, and Android invokes the corresponding wallet app to complete the transaction. The wallet app may present its own consent screen or require biometric confirmation.
Outcome: If the user consents, the selected identity credentials are shared with the requesting application. If the user declines, an error is returned.
Request Format for requesting ID credentials from wallet
Here is a sample of an mdoc requestJson
request to get Identity credentials
from any wallet on an Android device or web.
{
"providers": [{
"protocol": "openid4vp",
"request": "<credential_request>"
}]
}
Request Encryption
The client_metadata
contains the encryption public key for each request.
you'll have to store private keys for each request and use it to authenticate
and authorize the token that you receive from the wallet app.
The credential_request
parameter in requestJson
would consist the following
fields.
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt",
"nonce": "1234",
"origin": "https://www.website.com", // omit this parameter for app
"dcql_query": {
"credentials": [
{
"id": "cred1",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.18013.5.1.mDL" // this is for mDL. Use com.google.wallet.idcard.1 for ID pass
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
]
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
]
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
]
}
]
}
]
},
"client_metadata": {
"jwks": {
"keys": [ // sample request auth key
{
"kty": "EC",
"crv": "P-256",
"x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
"y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ"
}
]
},
"authorization_encrypted_response_alg": "ECDH-ES",
"authorization_encrypted_response_enc": "A128GCM"
}
}
You can request for any number of supported attributes from any identity credential stored in Google Wallet.
In App
To request identity credentials from your Android apps follow these steps:
Update dependencies
In your project's build.gradle, update your dependencies to use Credential Manager (beta):
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
// optional - needed for credentials support from play services, for devices running Android 13 and below.
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Configure the Credential Manager
To configure and initialize a CredentialManager
object, add logic similar
to the following:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
Request Identity attributes
Instead of specifying individual parameters for identity requests, the app provides them all together as a JSON string within the CredentialOption. The Credential Manager passes this JSON string along to the available digital wallets without examining its contents. Each wallet is then responsible for: - Parsing the JSON string to understand the identity request. - Determining which of its stored credentials, if any, satisfy the request.
We recommend partners to create their requests on the server even for android app integrations.
you'll use the requestJson
from Request Format
consisting the request
in GetDigitalCredentialOption()
function call
// The request in the JSON format to conform with
// the JSON-ified Digital Credentials API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
GetDigitalCredentialOption(requestJson = requestJson)
// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
listOf(digitalCredentialOption)
)
coroutineScope.launch {
try {
val result = credentialManager.getCredential(
context = activityContext,
request = getCredRequest
)
verifyResult(result)
} catch (e : GetCredentialException) {
handleFailure(e)
}
}
Verify and Validate the response
Once you get a response back from the wallet, you will verify if the response is
successful and contains the credentialJson
response.
// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
val credential = result.credential
when (credential) {
is DigitalCredential -> {
val responseJson = credential.credentialJson
validateResponseOnServer(responseJson) // make a server call to validate the response
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential ${credential.type}")
}
}
}
// Handle failure.
fun handleFailure(e: GetCredentialException) {
when (e) {
is GetCredentialCancellationException -> {
// The user intentionally canceled the operation and chose not
// to share the credential.
}
is GetCredentialInterruptedException -> {
// Retry-able error. Consider retrying the call.
}
is NoCredentialException -> {
// No credential was available.
}
else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
}
}
The credentialJson
response contain an encrypted identityToken (JWT),
defined by the W3C. The Wallet app is responsible for crafting this response.
Example:
"response" : {
<encrpted_response>
}
You'll pass this response back to the server to validate it's authenticity. You can find the steps to validate credential response.
Web
To request Identity Credentials using the Digital Credentials API on Chrome, you'll need to sign up for the Digital Credentials API origin trial.
const credentialResponse = await navigator.credentials.get({
digital: {
providers: [{
protocol: "openid4vp",
request: "<credential_request>"
}]
},
})
Send the response from this api back to your server to validate credential response
Steps to Validate credential response
Upon receiving the encrypted identityToken from your app or website there are multiple validations you'll need to perform before trusting the response.
Decrypt response using private key
The first step is to decrypt the token using the saved private key and get a response JSON.
Example:
TinkConfig.register(); // Sample Private Key JWK String privateKeysetJson = '{"crv":"P-256","d":"evjMOTTqWeTKKOrtWxq9kO_a5rHW_ja_wrT6eH7VPzs","kty":"EC","x":"C2Ka4HcNelUIxMjscNRq1GjFamP-fskGjvR6aY9Ac_Q","y":"a1tXuHhBnsGaaFyNzpqADY_Cp39Q56L2VLuADRsWncE"}'; // Read the private keyset for Hybrid Decryption KeysetHandle privateKeysetHandle = CleartextKeysetHandle.read( JsonKeysetReader.withString(privateKeysetJson)); HybridDecrypt decrypt = privateKeysetHandle.getPrimitive(HybridDecrypt.class); // Split the JWE string by the '.' delimiter String[] parts = jweString.split("\\."); if (parts.length != 5) { throw new IllegalArgumentException("Invalid JWE format (expected 5 parts)"); } // The encrypted data is typically in the second part (index 1) byte[] ciphertext = Base64.getUrlDecoder().decode(parts[1]); // Associated data (AAD) is often the third part (index 2) byte[] associatedData = Base64.getUrlDecoder().decode(parts[2]); // Decrypt the ciphertext byte[] decryptedPayloadBytes = decrypt.decrypt(ciphertext, associatedData); String decryptedJsonResponse = new String(decryptedPayloadBytes, StandardCharsets.UTF_8);
decrypted_json_response
will result in avp_token
JSON containing the credential{ "vp_token": { "cred1": "<credential_token>" } }
Create the session transcript
Next step is to create the SessionTranscript from ISO/IEC 18013-5:2021 with an Android or Web specific Handover structure:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]
For both Android / web handovers you'll need to use the same nonce that you used to generate
credential_request
.Android Handover
AndroidHandoverData = [ origin, // "android-origin:
", clientId, // "android:apk-key-hash:", nonce, // nonce that was used to generate credential request ] AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()Browser Handover
BrowserHandoverData =[ origin, // Origin URL clientId, // "web-origin:
" nonce, // nonce that was used to generate credential request ] BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()Using the
SessionTranscript
the DeviceResponse must be validated according to ISO/IEC 18013-5:2021 clause 9. This includes several steps, such as:Check State Issuer Cert. Checkout the supported issuer's IACA certs
Verify MSO signature (18013-5 Section 9.1.2)
Calculate and check ValueDigests for Data Elements (18013-5 Section 9.1.2)
Verify
deviceSignature
signature (18013-5 Section 9.1.3)
{
"version": "1.0",
"documents": [
{
"docType": "org.iso.18013.5.1.mDL",
"issuerSigned": {
"nameSpaces": {...}, // contains data elements
"issuerAuth": [...] // COSE_Sign1 w/ issuer PK, mso + sig
},
"deviceSigned": {
"nameSpaces": 24(<< {} >>), // empty
"deviceAuth": {
"deviceSignature": [...] // COSE_Sign1 w/ device signature
}
}
}
],
"status": 0
}
Test your solution
To test your solution, build and run our open source reference holder Android application. Here are the steps to build and run the reference holder app:
- Clone the reference apps repository
- Open the project on Android Studio
- Build and run the
appholder
target on your Android device or emulator.