Übertragung von Anmeldedaten

Mit den Credentials Transfer APIs des Credential Managers können Anmeldedaten von Nutzern sicher zwischen Anmeldedatenanbietern auf demselben Gerät übertragen werden. In diesem Leitfaden wird beschrieben, wie Anbieter von Anmeldedaten unter Android die APIs der androidx.credentials:providerevents-Bibliothek einbinden können. Diese Funktion unterstützt Passwörter, Passkeys, Adressinformationen und benutzerdefinierte Felder im standardisierten FIDO Credential Exchange Format (CXF).

Wichtige Konzepte

Das Framework für die Übertragung von Anmeldedaten ermöglicht die Peer-to-Peer-Übertragung von Anmeldedaten auf demselben Gerät, ohne dass die Rohdaten der Anmeldedaten dem Android-Betriebssystem oder nicht authentifizierten Apps zugänglich gemacht werden.

Das Framework definiert zwei primäre Rollen:

  • Exporter (Quellanbieter): Ein Anbieter von Anmeldedaten, der derzeit Nutzeranmeldedaten enthält. Sie registriert Metadaten zu verfügbaren exportierbaren Konten (ExportEntry) im System vor und reagiert auf Übertragungsanfragen, wenn sie vom Nutzer ausgewählt wird.
  • Importer (Anbieter von Anmeldedaten oder Einrichtungsassistent): Ein Anbieter von Anmeldedaten oder ein Einrichtungsassistent, der eine Importanfrage (ImportCredentialsRequest) initiiert und angibt, welche Arten von Anmeldedaten und Erweiterungen er empfangen kann.

Kompatibilität mit Android-Versionen

Die Credentials Transfer API funktioniert auf Geräten mit Android 8 (API-Level 26) und höher.

Abhängigkeiten hinzufügen

Fügen Sie der Datei build.gradle oder build.gradle.kts Ihres Moduls die Abhängigkeit androidx.credentials:providerevents hinzu:

dependencies {
    implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}

Erforderliche Klasse instanziieren

Erstellen Sie eine Instanz des erforderlichen ProviderEventsManager.

val providerEventsManager = ProviderEventsManager.create(context)

Exporter implementieren

Wenn Sie Nutzern erlauben möchten, Anmeldedaten aus Ihrer App in andere Anmeldedatenanbieter auf dem Gerät zu exportieren, implementieren Sie die Exporter-Rolle.

Exportierte Einträge registrieren

Wenn sich Ihr Anbieter von Anmeldedaten ändert (z. B. wenn sich ein Nutzer anmeldet, Anmeldedaten hinzufügt oder Konten ändert), registrieren oder aktualisieren Sie Ihre ExportEntry-Elemente mit ProviderEventsManager.registerExport().

Für jede ExportEntry ist Folgendes erforderlich:

  • id: Eine geheime, zufällig generierte String-ID, die diesen Exporteintrag eindeutig darstellt. Sie müssen diese ID persistent speichern, da Sie sie später zur Überprüfung eingehender Übertragungsanfragen benötigen.
  • accountDisplayName: Optionales Kontolabel (z. B. "Personal Account").
  • userDisplayName: Die primäre Kennung des Nutzers (z. B. "alice@example.com").
  • icon: Ein Bitmap-Symbol, das den Anbieter oder das Konto darstellt (wird von der Bibliothek automatisch auf 32 × 32 Pixel skaliert).
  • supportedCredentialTypes: Eine Reihe von Stringkonstanten aus CredentialTypes, die die Typen dieses Eintrags darstellen.

suspend fun registerMyProviderForExport(
    providerEventsManager: ProviderEventsManager,
    providerIcon: Bitmap,
    // Randomly generated and stored in encrypted storage
    secretEntryId: String
) {
    val entry = ExportEntry(
        id = secretEntryId,
        accountDisplayName = "MyProvider Personal",
        userDisplayName = "alice@example.com",
        icon = providerIcon,
        supportedCredentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD
        )
    )

    // RegisterExportRequest.create() attaches the default WASM matcher from assets
    val request = RegisterExportRequest.create(context, listOf(entry))

    try {
        val response = providerEventsManager.registerExport(request)
        // Registration successful
    } catch (e: Exception) {
        // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException)
    }
}

Exporter-Aktivität in der Manifestdatei deklarieren

Wenn der Nutzer deine ExportEntry in der Systemauswahl auswählt, startet das System die von dir festgelegte Activity. Deklarieren Sie diese Aktivität mit der obligatorischen Intent-Aktion und dem Inhalts-URI-Schema.

Um sich vor unbefugten Aufrufen und Confused-Deputy-Angriffen zu schützen, deklarieren Sie android:requireContentUriPermissionFromCaller="write" unter Android 14 (API-Level 34) und höher. Dadurch wird das Android-Betriebssystem angewiesen, zu prüfen, ob der Aufrufer, der den Start initiiert, Schreibberechtigungen für den Zielinhalts-URI hat, bevor die Aktivität gestartet werden darf. Dies wird im folgenden Beispiel gezeigt:

<activity
    android:name="com.example.CredentialExportActivity"
    android:exported="true"
    android:requireContentUriPermissionFromCaller="write"
    android:label="@string/export_activity_label">
    <intent-filter>
        <!-- This intent action is required for Credential Manager to invoke this activity -->
        <action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="content" />
    </intent-filter>
</activity>

Übertragungs-Intent in deiner Aktivität verarbeiten

Verwenden Sie in Ihrer CredentialExportActivity IntentHandler.retrieveProviderImportCredentialsRequest(intent), um die Anfrage zu parsen, die aufrufende Komponente und die Anfragemetadaten zu prüfen, die erforderliche biometrische Authentifizierung durchzuführen und die FIDO-CXF-Nutzlast zurück in den angegebenen Inhalts-URI zu schreiben.

Anfrage authentifizieren

Da Ihre Aktivität exportiert wird, sollten Sie den Ablauf des Exportvorgangs für Anmeldedaten schützen, indem Sie die folgenden Ebenen prüfen:

  1. credId-Sitzungs-Nonce prüfen (primäre Abwehr): Generiere bei der Registrierung deiner ExportEntry eine kryptografisch zufällige id (Nonce) mit hoher Entropie und verknüpfe sie mit einer kurzlebigen Sitzung. Prüfen Sie in Ihrer Aktivität, ob request.credId mit einer aktiven, nicht abgelaufenen Sitzungs-Nonce übereinstimmt, die zuvor von Ihrem Anbieter ausgestellt wurde. Da Drittanbieter-Apps diese Nonce nicht erraten können, wird so sichergestellt, dass der Intent einer authentischen Nutzerauswahl in der Systemauswahl entspricht.
  2. Activity.getCallingPackage() überprüfen (Anruferprüfung): Da die Auswahl der Anmeldedaten Ihre Aktivität mit startActivityForResult startet, kann Ihre Aktivität getCallingPackage() prüfen, um zu bestätigen, dass der unmittelbare Anrufer, der die Benutzeroberfläche startet, das vertrauenswürdige System-Framework für die Auswahl der Anmeldedaten ist (auf Android-Geräten mit Google Play-Diensten "com.google.android.gms"). Lehnen Sie die Anfrage ab, wenn das aufrufende Paket nicht erkannt wird.
  3. request.callingAppInfo (Informationen zur Zielanwendung) prüfen: Die callingAppInfo identifiziert die Zielanwendung, die die importierten Anmeldedaten erhält. Sie können diese Informationen nach eigenem Ermessen für die Transparenz verwenden (z. B. den Namen und das Symbol der Ziel-App in der Bestätigungs-UI anzeigen) oder prüfen, ob die Zielanwendung vertrauenswürdig ist.

class CredentialExportActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Extract the transfer request from the incoming Intent
        val request: ProviderImportCredentialsRequest? =
            IntentHandler.retrieveProviderImportCredentialsRequest(intent)

        if (request == null) {
            finishWithError()
            return
        }

        // 2. Validate CallingAppInfo and secret `credId`
        val callingAppPackage = request.callingAppInfo.packageName
        val receivedCredId = request.credId
        if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) {
            // Secret ID mismatch or untrusted caller -> abort
            sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request"))
            return
        }

        // 3. Optional: Prompt user for Biometric / PIN authentication before exporting
        authenticateUserThenExport(request)
    }

    private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) {
        // ... Biometric prompt logic ...
        // Once authenticated, generate the FIDO CXF JSON string matching the requested types
        val cxfJsonPayload = buildFidoCxfJsonPayload(
            requestedTypes = request.request.credentialTypes,
            requestedExtensions = request.request.knownExtensions
        )

        val response = ImportCredentialsResponse(cxfJsonPayload)

        // 4. Write the JSON payload to the Content URI and set Activity result
        IntentHandler.setImportCredentialsResponse(
            context = this,
            uri = request.uri,
            intent = intent,
            response = response
        )
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) {
        IntentHandler.setImportCredentialsException(intent, exception)
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun verifySecretEntryId(credentialId: String): Boolean {
        // Check if credentialId matches what you stored when calling RegisterExportRequest
        return credentialId == getStoredSecretEntryId()
    }

    private fun isTrustedImporter(packageName: String): Boolean {
        // Implement any specific allowlisting / caller checks if required
        return true
    }

    private fun finishWithError() {
        setResult(Activity.RESULT_CANCELED)
        finish()
    }
}

Ausnahmen für die Exportregistrierung und -freigabe

Alle Ausnahmen in diesem Modul sind Unterklassen von ImportCredentialsException, RegisterExportException oder ClearExportException.

Importer implementieren

Wenn Sie Anmeldedaten in Ihre App importieren möchten (z. B. während des Onboardings oder des Anbieterimports), implementieren Sie die Importeurrolle und starten Sie den Vorgang, indem Sie ProviderEventsManager.importCredentials() aufrufen.

Importanfrage erstellen und Ablauf starten

Geben Sie an, welche CredentialTypes und KnownExtensions Ihr Importer unterstützt:

suspend fun startCredentialImport(
    activityContext: Context,
    providerEventsManager: ProviderEventsManager
) {
    val importRequest = ImportCredentialsRequest(
        credentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH,
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY,
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_NOTE
        ),
        knownExtensions = setOf(
            KnownExtensions.KNOWN_EXTENSION_SHARED
        )
    )

    try {
        // Launches the system Selector UI; suspends until user selects a provider and completes transfer
        val response = providerEventsManager.importCredentials(activityContext, importRequest)

        // 1. Inspect the source exporter's package info
        val exporterPackageName = response.callingAppInfo.packageName

        // 2. Parse the FIDO CXF JSON string
        val cxfJsonString = response.response.responseJson
        parseAndSaveImportedCredentials(cxfJsonString)
    } catch (e: ImportCredentialsException) {
        // Handle specific import exceptions (e.g., ImportCredentialsCancellationException)
        handleImportFailure(e)
    }
}

private fun parseAndSaveImportedCredentials(cxfJsonString: String) {
    val rootJson = JSONObject(cxfJsonString)
    // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification:
    // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html
}

// Helper function to make it compile
private fun handleImportFailure(e: ImportCredentialsException) {}

Ausnahmen für Importabläufe

Alle Ausnahmen in diesem Modul sind Unterklassen von ImportCredentialsException, RegisterExportException oder ClearExportException.

Unterstützte Anmeldedatentypen und ‑erweiterungen

Das androidx.credentials.providerevents.transfer.CredentialTypes-Objekt definiert standardisierte Stringkonstanten, die den FIDO-CXF-Elementtypen entsprechen:

Konstante Wert (Typ cxf) Beschreibung
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" Anmeldedaten (Nutzername und Passwort)
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" FIDO2- oder WebAuthn-Passkey-Anmeldedaten mit öffentlichem Schlüssel.
CREDENTIAL_TYPE_ADDRESS "address" Post- oder Versandadressen für das automatische Ausfüllen von Formularen.
CREDENTIAL_TYPE_API_KEY "api-key" API-Zugriffsschlüssel und -Tokens.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" Zahlungsinformationen für Kredit- und Debitkarten
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" Benutzerdefinierte Gruppierungen oder benutzerdefinierte Felder.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" Führerscheindaten
CREDENTIAL_TYPE_FILE "file" Metadaten und Platzhalterverweise für Binärdateien.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" Maschinell generierte sichere Passwörter.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" Personalausweise, SSN, TIN oder Reisepassreferenzen.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" Logische Links, die auf ein anderes Element in der Nutzlast verweisen.
CREDENTIAL_TYPE_NOTE "note" Benutzerdefinierte sichere Notizen (UTF-8-String).
CREDENTIAL_TYPE_PASSPORT "passport" Details zum Reisepass.
CREDENTIAL_TYPE_PERSON_NAME "person-name" Identitäts- und Namensdetails der Person.
CREDENTIAL_TYPE_SSH_KEY "ssh-key" Öffentliche und private SSH-Schlüsselpaare.
CREDENTIAL_TYPE_TOTP "totp" Zeitbasierte Einmalpasswort-Secrets (2FA).
CREDENTIAL_TYPE_WIFI "wifi" SSID und Passphrasen des WLANs

Benutzerdefinierte WASM-Matcher (WebAssembly) (erweitert)

Standardmäßig werden beim Aufrufen von RegisterExportRequest.create(context, entries) die systemstandardmäßigen credential_transfer_matcher.wasm aus Bibliotheks-Assets gebündelt, wodurch Einträge nur anhand der Schnittmenge von supportedCredentialTypes gefiltert werden.

Wenn Ihr Anbieter von Anmeldedaten eine komplexe Abgleichslogik erfordert (z. B. dynamische Funktionsprüfungen oder bedingte Filterung basierend auf benutzerdefinierten Feldern), können Sie ein eigenes WebAssembly-Modul kompilieren, das der WASM-API für die Übertragung von Anmeldedaten entspricht, und das Rohbyte-Array direkt übergeben:

// Loading a custom WASM matcher
val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() }
val customRequest = RegisterExportRequest(
    entries = myEntries,
    exportMatcher = customMatcherBytes
)
providerEventsManager.registerExport(customRequest)