Anmeldedaten-Manager in die Lösung Ihres Anmeldedaten-Anbieters einbinden

Der Anmeldedaten-Manager bezieht sich auf eine Reihe von APIs, die in Android 14 eingeführt wurden und mehrere Anmeldemethoden wie Nutzername/Passwort, Passkeys und Lösungen für die föderierte Anmeldung (z. B. „Über Google anmelden“) unterstützen. Wenn die Credential Manager API aufgerufen wird, aggregiert das Android-System Anmeldedaten von allen auf dem Gerät installierten Anmeldedatenanbietern. In diesem Dokument werden die APIs beschrieben, die Integrationsendpunkte für diese Anbieter von Anmeldedaten bereitstellen.

Einrichten

Bevor Sie die Funktionen bei Ihrem Anmeldedatenanbieter implementieren, führen Sie die in den folgenden Abschnitten beschriebenen Einrichtungsschritte aus.

Abhängigkeiten deklarieren

Deklarieren Sie in der Datei build.gradle Ihres Moduls eine Abhängigkeit mithilfe der neuesten Version der Bibliothek des Anmeldedaten-Managers:

implementation "androidx.credentials:credentials:1.2.0-{latest}"

Dienstelement in Manifestdatei deklarieren

Füge in der Manifestdatei AndroidManifest.xml deiner App eine <service>-Deklaration für eine Dienstklasse ein, die die Klasse CredentialProviderService aus der androidx.credentials-Bibliothek erweitert, wie im folgenden Beispiel gezeigt.

<service android:name=".MyCredentialProviderService"
         android:enabled="true"
         android:exported="true"
         android:label="My Credential Provider"
         android:icon="<any drawable icon>"
         android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE">
    <intent-filter>
        <action android:name="android.service.credentials.CredentialProviderService"/>
    </intent-filter>
    <meta-data
         android:name="android.credentials.provider"
         android:resource="@xml/provider"/>
</service>

Die oben gezeigten Berechtigungen und der Intent-Filter sind entscheidend dafür, dass der Anmeldedaten-Manager wie erwartet funktioniert. Die Berechtigung ist erforderlich, damit nur das Android-System eine Verbindung zu diesem Dienst herstellen kann. Der Intent-Filter wird zur Auffindbarkeit dieses Dienstes als Anmeldedatenanbieter verwendet, der vom Anmeldedaten-Manager verwendet werden kann.

Unterstützte Anmeldedatentypen deklarieren

Erstellen Sie in Ihrem res/xml-Verzeichnis eine neue Datei mit dem Namen provider.xml. Deklarieren Sie in dieser Datei die von Ihrem Dienst unterstützten Anmeldedatentypen durch Konstanten, die für jeden Berechtigungstyp in der Bibliothek definiert sind. Im folgenden Beispiel unterstützt der Dienst sowohl traditionelle Passwörter als auch Passkeys, für die Konstanten als TYPE_PASSWORD_CREDENTIAL und TYPE_PUBLIC_KEY_CREDENTIAL definiert sind:

<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
   <capabilities>
       <capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
       <capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
   </capabilities>
</credential-provider>

Auf früheren API-Ebenen integrieren Anbieter von Anmeldedaten APIs wie das automatische Ausfüllen für Passwörter und andere Daten. Diese Anbieter können dieselbe interne Infrastruktur zum Speichern der vorhandenen Anmeldedatentypen verwenden und sie gleichzeitig für andere Typen, einschließlich Passkeys, ausweiten.

Zweiphasiger Ansatz zur Interaktion mit Dienstleistern

Die Interaktion zwischen dem Anmeldedaten-Manager und den Anbietern von Anmeldedaten erfolgt in zwei Phasen:

  1. Die erste Phase ist die Beginn/Abfragephase, in der sich das System an Dienste des Anmeldedatenanbieters bindet und die Methoden onBeginGetCredentialRequest(), onBeginCreateCredentialRequest() oder onClearCredentialStateRequest() mit Anfragen vom Typ Begin… aufruft. Anbieter müssen diese Anfragen verarbeiten und mit Begin…-Antworten als Antwort mit Einträgen versehen, die visuelle Optionen darstellen, die in der Kontoauswahl angezeigt werden sollen. Für jeden Eintrag muss ein PendingIntent festgelegt sein.
  2. Sobald der Nutzer einen Eintrag auswählt, beginnt die Auswahlphase. Die mit dem Eintrag verknüpfte PendingIntent wird ausgelöst, um die entsprechende Anbieteraktivität aufzurufen. Nachdem der Nutzer mit dieser Aktivität interagiert hat, muss der Anbieter von Anmeldedaten die Antwort auf das Ergebnis der Aktivität festlegen, bevor er sie beendet. Diese Antwort wird dann an die Clientanwendung gesendet, die den Anmeldedaten-Manager aufgerufen hat.

Passkey-Erstellung verarbeiten

Abfragen zum Erstellen von Passkeys verarbeiten

Wenn eine Client-App einen Passkey erstellen und bei einem Anmeldedatenanbieter speichern möchte, ruft sie die createCredential API auf. Führe die Schritte in den folgenden Abschnitten aus, um diese Anfrage in deinem Dienst für Anmeldedatenanbieter so zu verarbeiten, dass der Passkey tatsächlich in deinem Speicher gespeichert wird.

  1. Überschreiben Sie die Methode onBeginCreateCredentialRequest() in Ihrem Dienst, der von CredentialProviderService erweitert wurde.
  2. Erstellen Sie zur Verarbeitung des BeginCreateCredentialRequest ein entsprechendes BeginCreateCredentialResponse-Objekt und übergeben Sie es über den Callback.
  3. Fügen Sie beim Erstellen des BeginCreateCredentialResponse die erforderlichen CreateEntries hinzu. Jedes CreateEntry sollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können, und es muss ein PendingIntent-Objekt sowie andere erforderliche Metadaten festgelegt sein.

Das folgende Beispiel veranschaulicht, wie diese Schritte implementiert werden.

override fun onBeginCreateCredentialRequest(
  request: BeginCreateCredentialRequest,
  cancellationSignal: CancellationSignal,
  callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,
) {
  val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
  if (response != null) {
    callback.onResult(response)
  } else {
    callback.onError(CreateCredentialUnknownException())
  }
}

fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
  when (request) {
    is BeginCreatePublicKeyCredentialRequest -> {
      // Request is passkey type
      return handleCreatePasskeyQuery(request)
    }
  }
  // Request not supported
  return null
}

private fun handleCreatePasskeyQuery(
    request: BeginCreatePublicKeyCredentialRequest
    ): BeginCreateCredentialResponse {

    // Adding two create entries - one for storing credentials to the 'Personal'
    // account, and one for storing them to the 'Family' account. These
    // accounts are local to this sample app only.
    val createEntries: MutableList<CreateEntry> = mutableListOf()
    createEntries.add( CreateEntry(
        PERSONAL_ACCOUNT_ID,
        createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
    ))

    createEntries.add( CreateEntry(
        FAMILY_ACCOUNT_ID,
        createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
    ))

    return BeginCreateCredentialResponse(createEntries)
}

private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
    val intent = Intent(action).setPackage(PACKAGE_NAME)

    // Add your local account ID as an extra to the intent, so that when
    // user selects this entry, the credential can be saved to this
    // account
    intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)

    return PendingIntent.getActivity(
        applicationContext, UNIQUE_REQ_CODE,
        intent, (
            PendingIntent.FLAG_MUTABLE
            or PendingIntent.FLAG_UPDATE_CURRENT
        )
    )
}

Die Konstruktion von PendingIntent sollte den folgenden Anforderungen entsprechen:

  • Die entsprechende Aktivität sollte so eingerichtet werden, dass alle erforderlichen biometrischen Aufforderungen, Bestätigungen oder Auswahlen angezeigt werden.
  • Alle erforderlichen Daten, die der Anbieter benötigt, wenn die entsprechende Aktivität aufgerufen wird, sollten als Extra für den Intent festgelegt werden, der zum Erstellen der PendingIntent verwendet wird, z. B. ein accountId im Erstellungsablauf.
  • Ihre PendingIntent muss mit dem Flag PendingIntent.FLAG_MUTABLE erstellt werden, damit das System die letzte Anfrage an das zusätzliche Intent-Element anhängen kann.
  • Ihr PendingIntent darf nicht mit dem Flag PendingIntent.FLAG_ONE_SHOT erstellt werden, da der Nutzer einen Eintrag auswählen kann. Dann kann er zurückgehen und ihn noch einmal auswählen. Das würde dazu führen, dass das PendingIntent zweimal ausgelöst wird.
  • Ihre PendingIntent muss mit einem eindeutigen Anfragecode erstellt werden, damit jeder Eintrag eine eigene entsprechende PendingIntent haben kann.

Auswahl von Einträgen für Anfragen zur Passkey-Erstellung verarbeiten

  1. Wenn der Nutzer eine zuvor ausgefüllte CreateEntry auswählt, wird die entsprechende PendingIntent aufgerufen und der zugehörige Anbieter Activity erstellt.
  2. Nachdem die Methode onCreate Ihrer Aktivität aufgerufen wurde, greifen Sie auf den zugehörigen Intent zu und übergeben Sie ihn an die Klasse PendingIntentHander, um den ProviderCreateCredentialRequest abzurufen.
  3. Extrahieren Sie requestJson, callingAppInfo und clientDataHash aus der Anfrage.
  4. Extrahieren Sie das lokale accountId aus dem Intent-Extra. Dies ist eine Beispielimplementierung für eine Anwendung, die nicht erforderlich ist. Mit dieser Konto-ID können die Anmeldedaten für diese Konto-ID gespeichert werden.
  5. Prüfen Sie die requestJson. Im folgenden Beispiel werden lokale Datenklassen wie PublicKeyCredentialCreationOptions verwendet, um die JSON-Eingabe gemäß der WebAuthn-Spezifikation in eine strukturierte Klasse zu konvertieren. Als Anmeldedatenanbieter können Sie diese durch Ihren eigenen Parser ersetzen.
  6. Prüfe den Asset-Link für die aufrufende App, wenn der Aufruf von einer nativen Android-App stammt.
  7. Authentifizierungsaufforderung anzeigen. Im folgenden Beispiel wird die Biometric API von Android verwendet.
  8. Wenn die Authentifizierung erfolgreich ist, generieren Sie ein credentialId und ein Schlüsselpaar.
  9. Speichern Sie den privaten Schlüssel in Ihrer lokalen Datenbank für callingAppInfo.packageName.
  10. Erstelle eine JSON-Antwort für die Web Authentication API, die aus dem öffentlichen Schlüssel und dem credentialId besteht. Im folgenden Beispiel werden lokale Dienstprogrammklassen wie AuthenticatorAttestationResponse und FidoPublicKeyCredential verwendet, die beim Erstellen einer JSON-Datei basierend auf der oben genannten Spezifikation helfen.Als Anmeldedatenanbieter können Sie diese Klassen durch Ihre eigenen Builder ersetzen.
  11. Erstellen Sie ein CreatePublicKeyCredentialResponse mit dem oben generierten JSON.
  12. Legen Sie CreatePublicKeyCredentialResponse als zusätzliches Element für einen Intent bis PendingIntentHander.setCreateCredentialResponse() fest und legen Sie diesen Intent auf das Ergebnis der Aktivität fest.
  13. Beende die Aktivität.

Das Codebeispiel unten veranschaulicht diese Schritte. Dieser Code muss in der Activity-Klasse verarbeitet werden, nachdem onCreate() aufgerufen wurde.

val request =
  PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)

val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
  val publicKeyRequest: CreatePublicKeyCredentialRequest =
    request.callingRequest as CreatePublicKeyCredentialRequest
  createPasskey(
    publicKeyRequest.requestJson,
    request.callingAppInfo,
    publicKeyRequest.clientDataHash,
    accountId
  )
}

fun createPasskey(
  requestJson: String,
  callingAppInfo: CallingAppInfo?,
  clientDataHash: ByteArray?,
  accountId: String?
) {
  val request = PublicKeyCredentialCreationOptions(requestJson)

  val biometricPrompt = BiometricPrompt(
    this,
    <executor>,
    object : BiometricPrompt.AuthenticationCallback() {
      override fun onAuthenticationError(
        errorCode: Int, errString: CharSequence
      ) {
        super.onAuthenticationError(errorCode, errString)
        finish()
      }

      override fun onAuthenticationFailed() {
        super.onAuthenticationFailed()
        finish()
      }

      override fun onAuthenticationSucceeded(
        result: BiometricPrompt.AuthenticationResult
      ) {
        super.onAuthenticationSucceeded(result)

        // Generate a credentialId
        val credentialId = ByteArray(32)
        SecureRandom().nextBytes(credentialId)

        // Generate a credential key pair
        val spec = ECGenParameterSpec("secp256r1")
        val keyPairGen = KeyPairGenerator.getInstance("EC");
        keyPairGen.initialize(spec)
        val keyPair = keyPairGen.genKeyPair()

        // Save passkey in your database as per your own implementation

        // Create AuthenticatorAttestationResponse object to pass to
        // FidoPublicKeyCredential

        val response = AuthenticatorAttestationResponse(
          requestOptions = request,
          credentialId = credentialId,
          credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
          origin = appInfoToOrigin(callingAppInfo),
          up = true,
          uv = true,
          be = true,
          bs = true,
          packageName = callingAppInfo.packageName
        )

        val credential = FidoPublicKeyCredential(
          rawId = credentialId, response = response
        )
        val result = Intent()

        val createPublicKeyCredResponse =
          CreatePublicKeyCredentialResponse(credential.json())

        // Set the CreateCredentialResponse as the result of the Activity
        PendingIntentHandler.setCreateCredentialResponse(
          result, createPublicKeyCredResponse
        )
        setResult(Activity.RESULT_OK, result)
        finish()
      }
    }
  )

  val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Use your screen lock")
    .setSubtitle("Create passkey for ${request.rp.name}")
    .setAllowedAuthenticators(
        BiometricManager.Authenticators.BIOMETRIC_STRONG
        /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
      )
    .build()
  biometricPrompt.authenticate(promptInfo)
}

fun appInfoToOrigin(info: CallingAppInfo): String {
  val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
  val md = MessageDigest.getInstance("SHA-256");
  val certHash = md.digest(cert)
  // This is the format for origin
  return "android:apk-key-hash:${b64Encode(certHash)}"
}

Abfragen für Anfragen zur Passworterstellung verarbeiten

So verarbeiten Sie Abfragen für Anfragen zur Passworterstellung:

  • Fügen Sie in der im vorherigen Abschnitt erwähnten Methode processCreateCredentialRequest() innerhalb des Switch-Blocks einen weiteren Fall für die Verarbeitung von Passwortanfragen hinzu.
  • Fügen Sie beim Erstellen des BeginCreateCredentialResponse den erforderlichen CreateEntries hinzu.
  • Jede CreateEntry sollte einem Konto entsprechen, in dem die Anmeldedaten gespeichert werden können, und es muss ein PendingIntent sowie andere Metadaten dafür festgelegt sein.

Das folgende Beispiel zeigt, wie diese Schritte implementiert werden:

fun processCreateCredentialRequest(
    request: BeginCreateCredentialRequest
  ): BeginCreateCredentialResponse? {
  when (request) {
    is BeginCreatePublicKeyCredentialRequest -> {
      // Request is passkey type
      return handleCreatePasskeyQuery(request)
    }

    is BeginCreatePasswordCredentialRequest -> {
    // Request is password type
      return handleCreatePasswordQuery(request)
    }
  }
  return null
}

private fun handleCreatePasswordQuery(
    request: BeginCreatePasswordCredentialRequest
  ): BeginCreateCredentialResponse {
  val createEntries: MutableList<CreateEntry> = mutableListOf()

  // Adding two create entries - one for storing credentials to the 'Personal'
  // account, and one for storing them to the 'Family' account. These
  // accounts are local to this sample app only.
  createEntries.add(
    CreateEntry(
      PERSONAL_ACCOUNT_ID,
      createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
    )
  )
  createEntries.add(
    CreateEntry(
      FAMILY_ACCOUNT_ID,
      createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
    )
  )

  return BeginCreateCredentialResponse(createEntries)
}

Auswahl von Einträgen für Anfragen zur Passworterstellung verarbeiten

Wenn der Nutzer eine ausgefüllte CreateEntry auswählt, wird der entsprechende PendingIntent ausgeführt und die zugehörige Aktivität aufgerufen. Rufen Sie den zugehörigen Intent auf, der in onCreate übergeben wurde, und übergeben Sie ihn an die Klasse PendingIntentHander, um die Methode ProviderCreateCredentialRequest abzurufen.

Das folgende Beispiel veranschaulicht, wie dieser Prozess implementiert wird. Dieser Code muss in der Methode onCreate() Ihrer Aktivität verarbeitet werden.

val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)

val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest

// Fetch the ID and password from the request and save it in your database
<your_database>.addNewPassword(
    PasswordInfo(
        request.id,
        request.password,
        createRequest.callingAppInfo.packageName
    )
)

//Set the final response back
val result = Intent()
val response = CreatePasswordResponse()
PendingIntentHandler.setCreateCredentialResponse(result, response)
setResult(Activity.RESULT_OK, result)
this@<activity>.finish()

Nutzeranmeldung

Die Nutzeranmeldung läuft so ab:

  • Wenn eine Clientanwendung versucht, einen Nutzer anzumelden, wird eine GetCredentialRequest-Instanz vorbereitet.
  • Das Android-Framework leitet diese Anfrage durch eine Bindung an diese Dienste an alle anwendbaren Anmeldedatenanbieter weiter.
  • Der Anbieterdienst empfängt dann ein BeginGetCredentialRequest mit einer Liste von BeginGetCredentialOption, von denen jeder Parameter enthält, mit denen übereinstimmende Anmeldedaten abgerufen werden können.

Führe die folgenden Schritte aus, um diese Anfrage in deinem Anmeldedaten-Anbieterdienst zu verarbeiten:

  1. Überschreiben Sie die Methode onBeginGetCredentialRequest(), um die Anfrage zu verarbeiten. Wenn deine Anmeldedaten gesperrt sind, kannst du sofort ein AuthenticationAction für die Antwort festlegen und den Callback aufrufen.

    private val unlockEntryTitle = "Authenticate to continue"
    
    override fun onBeginGetCredentialRequest(
        request: BeginGetCredentialRequest,
        cancellationSignal: CancellationSignal,
        callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,
    ) {
        if (isAppLocked()) {
            callback.onResult(BeginGetCredentialResponse(
                authenticationActions = mutableListOf(AuthenticationAction(
                    unlockEntryTitle, createUnlockPendingIntent())
                    )
                )
            )
            return
        }
        try {
            response = processGetCredentialRequest(request)
            callback.onResult(response)
        } catch (e: GetCredentialException) {
            callback.onError(GetCredentialUnknownException())
        }
    }
    

    Anbieter, bei denen das Entsperren der Anmeldedaten vor dem Zurückgeben von credentialEntries erforderlich ist, müssen einen ausstehenden Intent einrichten, der den Nutzer zum Entsperrvorgang der App weiterleitet:

    private fun createUnlockPendingIntent(): PendingIntent {
        val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME)
        return PendingIntent.getActivity(
        applicationContext, UNIQUE_REQUEST_CODE, intent, (
            PendingIntent.FLAG_MUTABLE
            or PendingIntent.FLAG_UPDATE_CURRENT
            )
        )
    }
    
  2. Rufen Sie Anmeldedaten aus Ihrer lokalen Datenbank ab und richten Sie sie mit CredentialEntries ein, damit sie in der Auswahl angezeigt werden. Für Passkeys kannst du credentialId als zusätzliches Extra für den Intent festlegen, um zu ermitteln, welchen Anmeldedaten er zugeordnet ist, wenn der Nutzer diesen Eintrag auswählt.

    companion object {
        // These intent actions are specified for corresponding activities
        // that are to be invoked through the PendingIntent(s)
        private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY"
        private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD"
    
    }
    
    fun processGetCredentialsRequest(
    request: BeginGetCredentialRequest
    ): BeginGetCredentialResponse {
        val callingPackage = request.callingAppInfo?.packageName
        val credentialEntries: MutableList<CredentialEntry> = mutableListOf()
    
        for (option in request.beginGetCredentialOptions) {
            when (option) {
                is BeginGetPasswordOption -> {
                    credentialEntries.addAll(
                            populatePasswordData(
                                callingPackage,
                                option
                            )
                        )
                    }
                    is BeginGetPublicKeyCredentialOption -> {
                        credentialEntries.addAll(
                            populatePasskeyData(
                                callingPackage,
                                option
                            )
                        )
                    )
                } else -> {
                    Log.i(TAG, "Request not supported")
                }
            }
        }
        return BeginGetCredentialResponse(credentialEntries)
    }
    
  3. Fragen Sie Anmeldedaten aus Ihrer Datenbank ab und erstellen Sie Passkey- und Passworteinträge, die ausgefüllt werden sollen.

    private fun populatePasskeyData(
        callingAppInfo: CallingAppInfo,
        option: BeginGetPublicKeyCredentialOption
    ): List<CredentialEntry> {
      val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
      val request = PublicKeyCredentialRequestOptions(option.requestJson)
      // Get your credentials from database where you saved during creation flow
      val creds = <getCredentialsFromInternalDb(request.rpId)>
      val passkeys = creds.passkeys
      for (passkey in passkeys) {
          val data = Bundle()
          data.putString("credId", passkey.credId)
          passkeyEntries.add(
              PublicKeyCredentialEntry(
                  context = applicationContext,
                  username = passkey.username,
                  pendingIntent = createNewPendingIntent(
                      GET_PASSKEY_INTENT_ACTION,
                      data
                  ),
                  beginPublicKeyCredentialOption = option,
                  displayName = passkey.displayName,
                  icon = passkey.icon
              )
          )
      }
      return passkeyEntries
    }
    
    // Fetch password credentials and create password entries to populate to
    // the user
    private fun populatePasswordData(
    callingPackage: String,
    option: BeginGetPasswordOption
    ): List<CredentialEntry> {
        val passwordEntries: MutableList<CredentialEntry> = mutableListOf()
    
        // Get your password credentials from database where you saved during
        // creation flow
        val creds = <getCredentialsFromInternalDb(callingPackage)>
        val passwords = creds.passwords
        for (password in passwords) {
            passwordEntries.add(
                PasswordCredentialEntry(
                    context = applicationContext,
                    username = password.username,
                    pendingIntent = createNewPendingIntent(
                    GET_PASSWORD_INTENT
                    ),
                    beginGetPasswordOption = option
                        displayName = password.username,
                    icon = password.icon
                )
            )
        }
        return passwordEntries
    }
    
    private fun createNewPendingIntent(
        action: String,
        extra: Bundle? = null
    ): PendingIntent {
        val intent = Intent(action).setPackage(PACKAGE_NAME)
        if (extra != null) {
            intent.putExtra("CREDENTIAL_DATA", extra)
        }
    
        return PendingIntent.getActivity(
            applicationContext, UNIQUE_REQUEST_CODE, intent,
            (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
        )
    }
    
  4. Nachdem Sie die Anmeldedaten abfragen und eingetragen haben, müssen Sie die Auswahlphase für die vom Nutzer ausgewählten Anmeldedaten ausführen, unabhängig davon, ob es sich um einen Passkey oder ein Passwort handelt.

Nutzerauswahl für Passkeys verarbeiten

  1. Rufen Sie in der Methode onCreate der entsprechenden Aktivität den zugehörigen Intent ab und übergeben Sie ihn an PendingIntentHandler.retrieveProviderGetCredentialRequest().
  2. Extrahieren Sie das GetPublicKeyCredentialOption aus der oben abgerufenen Anfrage. Extrahieren Sie anschließend requestJson und clientDataHash aus dieser Option.
  3. Extrahieren Sie das credentialId aus dem Intent-Extra, der vom Anmeldedatenanbieter beim Einrichten des entsprechenden PendingIntent-Objekts eingefügt wurde.
  4. Extrahieren Sie den Passkey mithilfe der oben genannten Anfrageparameter aus Ihrer lokalen Datenbank.
  5. Sicherstellen, dass der Passkey mit extrahierten Metadaten und Nutzerüberprüfung gültig ist

    val getRequest =
        PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
    val publicKeyRequest =
    getRequest.credentialOption as GetPublicKeyCredentialOption
    
    val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA")
    val credIdEnc = requestInfo.getString("credId")
    
    // Get the saved passkey from your database based on the credential ID
    // from the publickeyRequest
    val passkey = <your database>.getPasskey(credIdEnc)
    
    // Decode the credential ID, private key and user ID
    val credId = b64Decode(credIdEnc)
    val privateKey = b64Decode(passkey.credPrivateKey)
    val uid = b64Decode(passkey.uid)
    
    val origin = appInfoToOrigin(getRequest.callingAppInfo)
    val packageName = getRequest.callingAppInfo.packageName
    
    validatePasskey(
        publicKeyRequest.requestJson,
        origin,
        packageName,
        uid,
        passkey.username,
        credId,
        privateKey
    )
    
  6. Lassen Sie den Nutzer über eine biometrische Aufforderung (oder eine andere Assertion-Methode) validieren. Im folgenden Code-Snippet wird die Android Biometric API verwendet.

  7. Sobald die Authentifizierung erfolgreich ist, erstelle eine JSON-Antwort basierend auf der W3 Web Authentication Assertion-Spezifikation. Im folgenden Code-Snippet werden Hilfsdatenklassen wie AuthenticatorAssertionResponse verwendet, um strukturierte Parameter zu erfassen und in das erforderliche JSON-Format zu konvertieren. Die Antwort enthält eine digitale Signatur aus dem privaten Schlüssel einer WebAuthn-Anmeldedaten. Der Server der vertrauenden Partei kann diese Signatur verifizieren, um einen Nutzer vor der Anmeldung zu authentifizieren.

  8. Erstelle ein PublicKeyCredential mit dem oben generierten JSON und lege es auf ein finales GetCredentialResponse-Element fest. Legen Sie diese endgültige Antwort auf das Ergebnis dieser Aktivität fest.

Das folgende Beispiel zeigt, wie diese Schritte implementiert werden können:

val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)

val biometricPrompt = BiometricPrompt(
    this,
    <executor>,
    object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationError(
        errorCode: Int, errString: CharSequence
        ) {
            super.onAuthenticationError(errorCode, errString)
            finish()
        }

        override fun onAuthenticationFailed() {
            super.onAuthenticationFailed()
            finish()
        }

        override fun onAuthenticationSucceeded(
        result: BiometricPrompt.AuthenticationResult
        ) {
        super.onAuthenticationSucceeded(result)
        val response = AuthenticatorAssertionResponse(
            requestOptions = request,
            credentialId = credId,
            origin = origin,
            up = true,
            uv = true,
            be = true,
            bs = true,
            userHandle = uid,
            packageName = packageName
        )

        val sig = Signature.getInstance("SHA256withECDSA");
        sig.initSign(privateKey)
        sig.update(response.dataToSign())
        response.signature = sig.sign()

        val credential = FidoPublicKeyCredential(
            rawId = credId, response = response
        )
        val result = Intent()
        val passkeyCredential = PublicKeyCredential(credential.json)
        PendingIntentHandler.setGetCredentialResponse(
            result, GetCredentialResponse(passkeyCredential)
        )
        setResult(RESULT_OK, result)
        finish()
        }
    }
)

val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Use your screen lock")
    .setSubtitle("Use passkey for ${request.rpId}")
    .setAllowedAuthenticators(
            BiometricManager.Authenticators.BIOMETRIC_STRONG
            /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
        )
    .build()
biometricPrompt.authenticate(promptInfo)

Nutzerauswahl für die Passwortauthentifizierung verarbeiten

  1. Rufen Sie in der entsprechenden Aktivität den an onCreate übergebenen Intent auf und extrahieren Sie ProviderGetCredentialRequest mithilfe von PendingIntentHandler.
  2. Verwenden Sie GetPasswordOption in der Anfrage, um die Anmeldedaten für den eingehenden Paketnamen abzurufen.

    val getRequest =
    PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
    
    val passwordOption = getRequest.credentialOption as GetPasswordCredentialOption
    
    val username = passwordOption.username
    // Fetch the credentials for the calling app package name
    val creds = <your_database>.getCredentials(callingAppInfo.packageName)
    val passwords = creds.passwords
    val it = passwords.iterator()
    var password = ""
    while (it.hasNext() == true) {
        val passwordItemCurrent = it.next()
        if (passwordItemCurrent.username == username) {
           password = passwordItemCurrent.password
           break
        }
    }
    
  3. Legen Sie nach dem Abrufen die Antwort für die ausgewählten Passwortanmeldedaten fest.

    // Set the response back
    val result = Intent()
    val passwordCredential = PasswordCredential(username, password)
    PendingIntentHandler.setGetCredentialResponse(
    result, GetCredentialResponse(passwordCredential)
    )
    setResult(Activity.RESULT_OK, result)
    finish()
    

Auswahl eines Eintrags für die Authentifizierungsaktion verarbeiten

Wie bereits erwähnt kann ein Anmeldedatenanbieter einen AuthenticationAction festlegen, wenn die Anmeldedaten gesperrt sind. Wenn der Nutzer diesen Eintrag auswählt, wird die Aktivität aufgerufen, die der Intent-Aktionsgruppe in PendingIntent entspricht. Anmeldedatenanbieter können dann einen biometrischen Authentifizierungsvorgang oder einen ähnlichen Mechanismus zum Entsperren der Anmeldedaten verwenden. Bei Erfolg muss der Anmeldedatenanbieter ein BeginGetCredentialResponse erstellen, ähnlich wie oben beschrieben, wie die Anmeldung von Nutzern beschrieben wird, da die Anmeldedaten jetzt entsperrt werden. Diese Antwort muss dann über die Methode PendingIntentHandler.setBeginGetCredentialResponse() festgelegt werden, bevor der vorbereitete Intent als Ergebnis festgelegt wird und die Aktivität abgeschlossen ist.

Anmeldedaten-Anfragen löschen

Eine Clientanwendung kann anfordern, dass jeder für die Auswahl der Anmeldedaten beibehaltene Status gelöscht werden muss. So kann beispielsweise ein Anmeldedatenanbieter sich die zuvor ausgewählten Anmeldedaten merken und diese nur beim nächsten Mal zurückgeben. Eine Client-App ruft diese API auf und erwartet, dass die fixierte Auswahl gelöscht wird. Ihr Anmeldedatenanbieterdienst kann diese Anfrage verarbeiten, indem er die Methode onClearCredentialStateRequest() überschreibt:

override fun onClearCredentialStateRequest(
    request: android.service.credentials.ClearCredentialStateRequest,
    cancellationSignal: CancellationSignal,
    callback: OutcomeReceiver<Void?, ClearCredentialException>,
  ) {
    // Delete any maintained state as appropriate.
}

Zulassungsliste mit privilegierten Apps abrufen

Privilegierte Apps wie Webbrowser führen Aufrufe des Anmeldedaten-Managers im Namen anderer vertrauender Parteien aus. Dazu wird der Parameter origin in den Methoden GetCredentialRequest() und CreatePublicKeyCredentialRequest() des Anmeldedaten-Managers festgelegt. Zur Verarbeitung dieser Anfragen ruft der Anmeldedatenanbieter die origin mithilfe der getOrigin() API ab.

Zum Abrufen des origin muss die Anwendung des Anmeldedatenanbieters eine Liste privilegierter und vertrauenswürdiger Aufrufer an die androidx.credentials.provider.CallingAppInfo's getOrigin() API übergeben. Diese Zulassungsliste muss ein gültiges JSON-Objekt sein. Der origin wird zurückgegeben, wenn die von signingInfo abgerufenen packageName- und Zertifikat-Fingerabdrücke mit denen einer App übereinstimmen, die in der an die getOrigin() API übergebenen privilegedAllowlist gefunden wurde. Nachdem der Wert origin abgerufen wurde, sollte die Anbieter-App dies als privilegierten Aufruf betrachten und origin für die Clientdaten in AuthenticatorResponse festlegen, anstatt den origin mit der Signatur der aufrufenden App zu berechnen.

Wenn Sie ein origin abrufen, verwenden Sie das clientDataHash, das direkt in CreatePublicKeyCredentialRequest() oder GetPublicKeyCredentialOption() bereitgestellt wird, anstatt während der Signaturanfrage clientDataHash zusammenzustellen und zu hashen.clientDataJSON Legen Sie in der Attestierungs- und Assertion-Antwort einen Platzhalterwert für clientDataJSON fest, um Probleme beim JSON-Parsing zu vermeiden.

Der Google Passwortmanager verwendet eine öffentlich verfügbare Zulassungsliste für Aufrufe von getOrigin(). Als Anmeldedatenanbieter können Sie diese Liste verwenden oder Ihre eigene im JSON-Format angeben, wie in der API beschrieben. Der Anbieter entscheidet selbst, welche Liste verwendet wird. Informationen zum privilegierten Zugriff über Anmeldedaten von Drittanbietern finden Sie in der Dokumentation des Drittanbieters.

Anbieter auf einem Gerät aktivieren

Nutzer müssen den Anbieter über Geräteeinstellungen > Passwörter und Konten > Dein Anbieter > Aktivieren oder deaktivieren aktivieren.

Rufen Sie unter Android 14 oder höher die createSettingsPendingIntent() API auf, um beim Aufrufen einen ausstehenden Intent zurückzugeben. Daraufhin wird ein Bildschirm angezeigt, über den der Nutzer den Anbieter Ihres Anmeldedaten-Managers aktivieren kann.

fun createSettingsPendingIntent(): PendingIntent