Richiedi credenziali digitali in WebView

Questa guida descrive come richiedere credenziali digitali utilizzando Gestore delle credenziali in un'app per Android che utilizza WebView. Utilizza un bridge JavaScript per facilitare la comunicazione tra la pagina web e l'app.

Panoramica

I seguenti passaggi descrivono il ciclo di vita di una richiesta di credenziali digitali da una WebView:

  1. Generazione della richiesta: la pagina web nella WebView invia una richiesta al server dell'app, che genera un JSON di richiesta OpenID4VP e lo restituisce.
  2. Avvio della richiesta: la pagina web utilizza navigator.credentials.get({ digital: ... }) per richiedere le credenziali digitali utilizzando il file JSON della richiesta.
  3. Intercettazione: l'bridge.js inserito intercetta questa chiamata, estrae il file JSON della richiesta OpenID4VP specifico e lo passa all'app per Android.
  4. Gestione di Android: l'app per Android riceve il messaggio e chiama l'API CredentialManager.getCredential.
  5. Selezione dell'utente: Gestore delle credenziali mostra una UI che chiede all'utente di condividere la propria credenziale. La credenziale viene restituita all'app per Android.
  6. Risposta del bridge: l'app per Android invia la risposta alla pagina web utilizzando il bridge.
  7. Completamento: la pagina web riceve e gestisce la risposta.

Prerequisiti

Prima di implementare il bridge JavaScript per le credenziali digitali in WebView, assicurati che il tuo progetto soddisfi i seguenti requisiti:

  • Compatibilità con le versioni di Android: la tua app deve avere come target Android 6.0 (livello API 23) o versioni successive.
  • Controllo dell'app host: devi controllare l'app per Android che ospita WebView in modo da poter allegare un WebMessageListener prima di caricare la pagina web.
  • HTTPS e frame di primo livello: la pagina web che richiede la credenziale deve essere pubblicata tramite https:// ed eseguita nel frame principale (isMainFrame deve essere true). I frame secondari e gli elementi <iframe> incorporati non sono supportati.
  • Attivazione dell'API sperimentale: poiché l'API Digital Credentials è sperimentale, devi annotare le classi o i metodi che utilizzano GetDigitalCredentialOption con @OptIn(ExperimentalDigitalCredentialApi::class).

Aggiungi dipendenze

Aggiungi le dipendenze di Gestore delle credenziali e AndroidX WebKit al file build.gradle.kts del modulo dell'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 le credenziali digitali in WebView

Dovrai aggiungere JavaScript alla tua WebView per effettuare la richiesta di credenziali e intercettarla con bridge.js.

Crea la richiesta JavaScript

Per comunicare tra JavaScript in esecuzione in una WebView e il codice Android, inserisci il codice JavaScript nella 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 il bridge JavaScript

Crea un file denominato bridge.js o inserisci questo codice utilizzando evaluateJavascript. Questo script esegue l'override di navigator.credentials.get per intercettare la richiesta e inviare la stringa JSON OpenID4VP interna all'app per 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 le credenziali digitali in Android

Configura la tua app per Android in modo che gestisca le richieste di credenziali inviate da bridge.js.

Crea il listener Android

Configura una classe DigitalCredentialWebListener che implementa WebViewCompat.WebMessageListener. Questa classe gestisce i messaggi bidirezionali tra JavaScript e il codice Android nativo, applicando controlli di sicurezza su isMainFrame e sourceOrigin e impedendo richieste sovrapposte.

Implementa il gestore di Gestore delle credenziali

Chiama getCredential per richiedere le credenziali digitali dal 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"
  }
}

Applica il framework alla tua attività

Nel tuo Activity o View, registra WebMessageListener e le funzioni associate.

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

Test del flusso…

Testa il flusso per confermare la comunicazione tra WebView e l'app per Android. Il selettore delle credenziali viene visualizzato in WebView durante la procedura e la credenziale selezionata torna a WebView al termine.