This guide describes how to request digital credentials using Credential Manager in an Android app that uses WebView. Use a JavaScript Bridge to facilitate communication between the web page and the app.
Overview
The following steps describe the life of a digital credential request from a WebView:
- Request generation: The web page in the WebView makes a request to the app server, which generates an OpenID4VP request JSON and sends it back.
- Request initiation: The web page uses
navigator.credentials.get({ digital: ...}) to request digital credentials using the request JSON. - Interception: The injected
bridge.jsintercepts this call, extracts the specific OpenID4VP request JSON, and passes it to the Android app. - Android Handling: The Android app receives the message and calls the
CredentialManager.getCredentialAPI. - User selection: Credential Manager displays a UI prompting the user to share their credential. The credential is returned to the Android app.
- Bridge response: The Android app sends the response back to the web page using the bridge.
- Completion: The web page receives and handles the response.
Prerequisites
Before you implement the JavaScript bridge for digital credentials in WebView, ensure that your project meets the following requirements:
- Android version compatibility: Your app must target Android 6.0 (API level 23) or higher.
- Host app control: You must control the Android app hosting the
WebViewso that you can attach aWebMessageListenerbefore loading the web page. - HTTPS and top-level frame: The web page requesting the credential must be
served over
https://and run in the main frame (isMainFramemust betrue). Subframes and embedded<iframe>elements aren't supported. - Experimental API opt-in: Because the Digital Credentials API is
experimental, you must annotate classes or methods that use
GetDigitalCredentialOptionwith@OptIn(ExperimentalDigitalCredentialApi::class).
Add dependencies
Add the Credential Manager and AndroidX WebKit dependencies to your app module's
build.gradle.kts file:
dependencies {
// Credential Manager dependencies
implementation("androidx.credentials:credentials:1.7.0-alpha03")
implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha03")
// AndroidX WebKit (required for WebMessageListener bridge)
implementation("androidx.webkit:webkit:1.14.0")
}
Implement digital credentials in the WebView
You'll need to add JavaScript to your WebView to make the credential request
and intercept it with bridge.js.
Create the JavaScript request
To communicate between JavaScript running in a WebView and Android code, inject JavaScript code into your WebView:
// Example digital credential request to request verified email
const request = {
"providers": [
{
"protocol": "openid4vp",
"request": JSON.stringify({
"requests": [
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "n-0S6_W_2F", // Generate dynamically
"dcql_query": {
"credentials": [
{
"id": "user_info_query",
"format": "dc+sd-jwt",
"meta": {
"vct_values": ["UserInfoCredential"]
},
"claims": [
{"path": ["email"]},
{"path": ["email_verified"]},
{"path": ["name"]}
]
}
]
}
}
}
]
})
}
]
};
try {
const credential = await navigator.credentials.get({
digital: request
});
// Process response
} catch (err) {
// Handle error
}
Create the JavaScript bridge
Create a file named bridge.js or inject this code using evaluateJavascript.
This script overrides navigator.credentials.get to intercept the request and
send the inner OpenID4VP JSON string to the Android app.
(function() {
if (!navigator.credentials) {
navigator.credentials = {};
}
const originalGet = navigator.credentials.get;
// Overrides navigator.credentials.get() to intercept W3C Digital Credential requests.
navigator.credentials.get = async function(options) {
if (options && options.digital) {
if (window.DigitalCredentialBridge) {
return new Promise((resolve, reject) => {
const digital = options.digital;
if (digital.providers && digital.providers.length > 0) {
const provider = digital.providers[0];
if (provider.protocol === "openid4vp") {
const requestJson = provider.request;
const message = {
type: "get",
request: requestJson
};
console.log("Sending message to native bridge:", JSON.stringify(message));
// Send the request string to native Android code
window.DigitalCredentialBridge.postMessage(JSON.stringify(message));
// Receive the response sent from native code using replyProxy.postMessage()
window.DigitalCredentialBridge.onmessage = function(event) {
console.log("Received response from native bridge:", event.data);
try {
const response = JSON.parse(event.data);
if (response.error) {
reject(new Error(response.error));
} else {
resolve(response);
}
} catch (e) {
// If it's not JSON, just resolve with the raw data
resolve(event.data);
}
};
} else {
reject(new Error("Unsupported protocol: " + provider.protocol));
}
} else {
reject(new Error("No providers specified in digital credential request"));
}
});
} else {
console.warn("DigitalCredentialBridge not found, falling back to original");
}
}
if (originalGet) {
return originalGet.call(navigator.credentials, options);
} else {
throw new Error("navigator.credentials.get is not supported and no bridge available");
}
};
})();
Implement digital credentials in Android
Configure your Android app to handle credential requests sent from bridge.js.
Create the Android listener
Set up a DigitalCredentialWebListener class that implements
WebViewCompat.WebMessageListener. This class handles bidirectional messages
between JavaScript and native Android code, enforcing security checks on
isMainFrame and sourceOrigin and preventing overlapping requests.
Implement the Credential Manager Handler
Call getCredential to request digital credentials from the device.
@OptIn(ExperimentalDigitalCredentialApi::class)
class CredentialManagerHandler(private val context: Context) {
private val credentialManager = CredentialManager.create(context)
suspend fun getDigitalCredential(requestJson: String): String {
val digitalCredentialOption = GetDigitalCredentialOption(requestJson)
val getCredRequest = GetCredentialRequest(listOf(digitalCredentialOption))
return try {
val result = credentialManager.getCredential(context, getCredRequest)
val credential = result.credential
if (credential is DigitalCredential) {
credential.credentialJson
} else {
"{\"error\": \"Unexpected credential type\"}"
}
} catch (e: GetCredentialException) {
"{\"error\": \"${e.message}\"}"
} catch (e: Exception) {
"{\"error\": \"Unknown exception\"}"
}
}
companion object {
private const val TAG = "CredManHandler"
}
}
Apply the framework to your Activity
In your Activity or View, register the WebMessageListener and associated
functions.
// Inside your Activity or View setup
AndroidView(
factory = { context ->
val handler = CredentialManagerHandler(context)
WebView(context).apply {
settings.javaScriptEnabled = true
// Register the bridge before loading URL
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
val listener = DigitalCredentialWebListener(coroutineScope, credentialManagerHandler)
WebViewCompat.addWebMessageListener(
webView,
INTERFACE_NAME,
setOf("*"),
listener
)
loadUrl("https://your-secure-domain.com/login")
}
},
)
Testing the flow
Test the flow to confirm communication between the WebView and the Android app. The credential selector appears in the WebView during the process, and the selected credential returns to the WebView upon completion.