Cómo solicitar credenciales digitales en WebView

En esta guía, se describe cómo solicitar credenciales digitales con el Administrador de credenciales en una app para Android que usa WebView. Usa un puente de JavaScript para facilitar la comunicación entre la página web y la app.

Descripción general

En los siguientes pasos, se describe la vida de una solicitud de credencial digital desde un WebView:

  1. Generación de solicitudes: La página web en WebView realiza una solicitud al servidor de la app, que genera un JSON de solicitud de OpenID4VP y lo devuelve.
  2. Inicio de la solicitud: La página web usa navigator.credentials.get({ digital: ... }) para solicitar credenciales digitales con el JSON de la solicitud.
  3. Intercepción: El bridge.js insertado intercepta esta llamada, extrae el JSON de la solicitud de OpenID4VP específico y lo pasa a la app para Android.
  4. Control de Android: La app para Android recibe el mensaje y llama a la API de CredentialManager.getCredential.
  5. Selección del usuario: El Administrador de credenciales muestra una IU en la que se le solicita al usuario que comparta su credencial. La credencial se devuelve a la app para Android.
  6. Respuesta del puente: La app para Android envía la respuesta a la página web con el puente.
  7. Finalización: La página web recibe y controla la respuesta.

Requisitos previos

Antes de implementar el puente de JavaScript para credenciales digitales en WebView, asegúrate de que tu proyecto cumpla con los siguientes requisitos:

  • Compatibilidad con versiones de Android: Tu app debe orientarse a Android 6.0 (nivel de API 23) o versiones posteriores.
  • Control de la app host: Debes controlar la app para Android que aloja la WebView para poder adjuntar un WebMessageListener antes de cargar la página web.
  • HTTPS y marco de nivel superior: La página web que solicita la credencial debe entregarse a través de https:// y ejecutarse en el marco principal (isMainFrame debe ser true). No se admiten los submarcos ni los elementos <iframe> incorporados.
  • Habilitación de la API experimental: Como la API de Digital Credentials es experimental, debes anotar las clases o los métodos que usan GetDigitalCredentialOption con @OptIn(ExperimentalDigitalCredentialApi::class).

Cómo agregar dependencias

Agrega las dependencias del Administrador de credenciales y de WebKit de AndroidX al archivo build.gradle.kts del módulo de tu app:

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")
}

Implementa credenciales digitales en WebView

Deberás agregar JavaScript a tu WebView para realizar la solicitud de credenciales y, luego, interceptarla con bridge.js.

Crea la solicitud de JavaScript

Para establecer la comunicación entre JavaScript que se ejecuta en un WebView y el código de Android, inserta código de JavaScript en tu 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
}

Crea el puente de JavaScript

Crea un archivo llamado bridge.js o inserta este código con evaluateJavascript. Esta secuencia de comandos anula navigator.credentials.get para interceptar la solicitud y enviar la cadena JSON interna de OpenID4VP a la app para Android.

(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");
    }
  };
})();

Implementa credenciales digitales en Android

Configura tu app para Android para que controle las solicitudes de credenciales enviadas desde bridge.js.

Crea el objeto de escucha de Android

Configura una clase DigitalCredentialWebListener que implemente WebViewCompat.WebMessageListener. Esta clase controla los mensajes bidireccionales entre JavaScript y el código nativo de Android, aplica verificaciones de seguridad en isMainFrame y sourceOrigin, y evita las solicitudes superpuestas.

Implementa el controlador del Administrador de credenciales

Llama a getCredential para solicitar credenciales digitales del dispositivo.

@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"
  }
}

Aplica el framework a tu actividad

En tu Activity o View, registra el WebMessageListener y las funciones asociadas.

// 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")
    }
  },
)

Probando el flujo

Prueba el flujo para confirmar la comunicación entre WebView y la app para Android. El selector de credenciales aparece en WebView durante el proceso, y la credencial seleccionada vuelve a WebView cuando se completa.