Von FIDO2 zum Credential Manager migrieren

Der Anmeldedaten-Manager unterstützt Passkeys, föderierte Anmeldung und externe Authentifizierungsanbieter. Daher ist der Anmeldedaten-Manager die empfohlene API für die Authentifizierung unter Android. Sie bietet eine sichere und praktische Umgebung, in der Nutzer ihre Anmeldedaten synchronisieren und verwalten können. Entwickler, die lokale FIDO2-Anmeldedaten verwenden, sollten ihre Anwendung so aktualisieren, dass die Passkey-Authentifizierung durch Einbindung in die Credential Manager API unterstützt wird. In diesem Dokument wird beschrieben, wie Sie Ihr Projekt von FIDO2 zum Anmeldedaten-Manager migrieren.

Gründe für die Migration von FIDO2 zum Anmeldedaten-Manager

In den meisten Fällen sollten Sie den Authentifizierungsanbieter Ihrer Android-App zum Anmeldedaten-Manager migrieren. Zu den Gründen für die Migration zum Anmeldedaten-Manager gehören:

  • Passkey-Unterstützung:Die Anmeldedatenverwaltung unterstützt Passkeys, einen neuen Authentifizierungsmechanismus ohne Passwort, der sicherer und einfacher zu verwenden ist als Passwörter.
  • Mehrere Anmeldemethoden:Die Anmeldedatenverwaltung unterstützt mehrere Anmeldemethoden, einschließlich Passwörtern, Passkeys und föderierten Anmeldemethoden. Dadurch wird es für Nutzer einfacher, sich bei Ihrer Anwendung zu authentifizieren, unabhängig von ihrer bevorzugten Authentifizierungsmethode.
  • Unterstützung von Drittanbietern von Anmeldedaten:Unter Android 14 und höher unterstützt die Anmeldedaten-Manager mehrere Drittanbieter von Anmeldedaten. Das bedeutet, dass Ihre Nutzer ihre vorhandenen Anmeldedaten von anderen Anbietern verwenden können, um sich in Ihrer Anwendung anzumelden.
  • Einheitliche Nutzererfahrung: Die Anmeldedatenverwaltung bietet eine einheitlichere Nutzererfahrung bei der Authentifizierung über mehrere Anwendungen und Anmeldemechanismen hinweg. Dies erleichtert Nutzern das Verständnis und die Verwendung des Authentifizierungsvorgangs Ihrer Anwendung.

Führen Sie die folgenden Schritte aus, um die Migration von FIDO2 zum Anmeldedaten-Manager zu starten.

Abhängigkeiten aktualisieren

  1. Aktualisieren Sie das Kotlin-Plug-in in der Datei build.gradle Ihres Projekts auf Version 1.8.10 oder höher.

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. Aktualisieren Sie die Abhängigkeiten in der Datei build.gradle Ihres Projekts, um die Anmeldedatenverwaltung und die Authentifizierung der Play-Dienste zu verwenden.

    dependencies {
      // ...
      // Credential Manager:
      implementation 'androidx.credentials:credentials:<latest-version>'
    
      // Play Services Authentication:
      // Optional - needed for credentials support from play services, for devices running
      // Android 13 and below:
      implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>'
      // ...
    }
    
  3. Ersetzen Sie die FIDO-Initialisierung durch die Initialisierung des Anmeldedaten-Managers. Fügen Sie diese Deklaration in die Klasse ein, die Sie für die Methoden zum Erstellen von Passkeys und Anmeldemethoden verwenden:

    val credMan = CredentialManager.create(context)
    

Passkeys erstellen

Du musst einen neuen Passkey erstellen, ihn mit einem Nutzerkonto verknüpfen und den öffentlichen Schlüssel des Passkeys auf deinem Server speichern, damit sich der Nutzer damit anmelden kann. Richte deine App mit dieser Funktion ein, indem du die Registrierungsfunktionsaufrufe aktualisierst.

Abbildung 1: Diese Abbildung zeigt, wie Daten zwischen der App und dem Server ausgetauscht werden, wenn mit dem Anmeldedaten-Manager ein Passkey erstellt wird.
  1. Wenn du die erforderlichen Parameter abrufen möchtest, die während der Passkey-Erstellung an die Methode createCredential() gesendet werden, füge name("residentKey").value("required") wie in der WebAuthn-Spezifikation beschrieben in deinen registerRequest()-Serveraufruf ein.

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .method("POST", jsonRequestBody {
            name("attestation").value("none")
            name("authenticatorSelection").objectValue {
                name("residentKey").value("required")
            }
        }).build()
        // ...
    }
    
  2. Legen Sie den Typ return für registerRequest() und alle untergeordneten Funktionen auf JSONObject fest.

    suspend fun registerRequest(sessionId: String): ApiResult<JSONObject> {
        val call = client.newCall(
            Request.Builder()
                .url("$BASE_URL/<your api url>")
                .addHeader("Cookie", formatCookie(sessionId))
                .method("POST", jsonRequestBody {
                    name("attestation").value("none")
                    name("authenticatorSelection").objectValue {
                        name("authenticatorAttachment").value("platform")
                        name("userVerification").value("required")
                        name("residentKey").value("required")
                    }
                }).build()
        )
        val response = call.await()
        return response.result("Error calling the api") {
            parsePublicKeyCredentialCreationOptions(
                body ?: throw ApiException("Empty response from the api call")
            )
        }
    }
    
  3. Entfernen Sie sicher alle Methoden, die Intent Launcher- und Aktivitätsergebnisaufrufe aus Ihrer Ansicht verarbeiten.

  4. Da registerRequest() jetzt ein JSONObject zurückgibt, müssen Sie kein PendingIntent erstellen. Ersetzen Sie den zurückgegebenen Intent durch einen JSONObject. Aktualisieren Sie Ihre Intent-Launcher-Aufrufe, um createCredential() über die Credential Manager API aufzurufen. Rufen Sie die API-Methode createCredential() auf.

    suspend fun createPasskey(
        activity: Activity,
        requestResult: JSONObject
        ): CreatePublicKeyCredentialResponse? {
            val request = CreatePublicKeyCredentialRequest(requestResult.toString())
            var response: CreatePublicKeyCredentialResponse? = null
            try {
                response = credMan.createCredential(
                    request as CreateCredentialRequest,
                    activity
                ) as CreatePublicKeyCredentialResponse
            } catch (e: CreateCredentialException) {
    
                showErrorAlert(activity, e)
    
                return null
            }
            return response
        }
    
  5. Sobald der Aufruf erfolgreich war, senden Sie die Antwort zurück an den Server. Die Anfrage und die Antwort für diesen Aufruf ähneln der FIDO2-Implementierung, sodass keine Änderungen erforderlich sind.

Mit Passkeys authentifizieren

Nachdem du die Passkey-Erstellung eingerichtet hast, kannst du deine App so einrichten, dass sich Nutzer mit ihren Passkeys anmelden und sich mit ihnen authentifizieren können. Dazu aktualisieren Sie Ihren Authentifizierungscode, damit die Ergebnisse des Anmeldedaten-Managers verarbeitet werden können, und implementieren eine Funktion zur Authentifizierung über Passkeys.

Abbildung 2: Passkey-Authentifizierungsablauf des Anmeldedaten-Managers.
  1. Ihre Anmeldeanfrage an den Server, um die erforderlichen Informationen zum Senden an die getCredential()-Anfrage zu erhalten, entspricht der FIDO2-Implementierung. Es sind keine Änderungen erforderlich.
  2. Ähnlich wie beim Aufruf der Registrierungsanfrage hat die zurückgegebene Antwort das Format JSONObject.

    /**
     * @param sessionId The session ID to be used for the sign-in.
     * @param credentialId The credential ID of this device.
     * @return a JSON object.
     */
    suspend fun signinRequest(): ApiResult<JSONObject> {
        val call = client.newCall(Builder().url(buildString {
            append("$BASE_URL/signinRequest")
        }).method("POST", jsonRequestBody {})
            .build()
        )
        val response = call.await()
        return response.result("Error calling /signinRequest") {
            parsePublicKeyCredentialRequestOptions(
                body ?: throw ApiException("Empty response from /signinRequest")
            )
        }
    }
    
    /**
     * @param sessionId The session ID to be used for the sign-in.
     * @param response The JSONObject for signInResponse.
     * @param credentialId id/rawId.
     * @return A list of all the credentials registered on the server,
     * including the newly-registered one.
     */
    suspend fun signinResponse(
        sessionId: String, response: JSONObject, credentialId: String
        ): ApiResult<Unit> {
    
            val call = client.newCall(
                Builder().url("$BASE_URL/signinResponse")
                    .addHeader("Cookie",formatCookie(sessionId))
                    .method("POST", jsonRequestBody {
                        name("id").value(credentialId)
                        name("type").value(PUBLIC_KEY.toString())
                        name("rawId").value(credentialId)
                        name("response").objectValue {
                            name("clientDataJSON").value(
                                response.getString("clientDataJSON")
                            )
                            name("authenticatorData").value(
                                response.getString("authenticatorData")
                            )
                            name("signature").value(
                                response.getString("signature")
                            )
                            name("userHandle").value(
                                response.getString("userHandle")
                            )
                        }
                    }).build()
            )
            val apiResponse = call.await()
            return apiResponse.result("Error calling /signingResponse") {
            }
        }
    
  3. Entfernen Sie sicher alle Methoden, die den Intent Launcher und Aktivitätsergebnisaufrufe verarbeiten, aus Ihrer Ansicht.

  4. Da signInRequest() jetzt ein JSONObject zurückgibt, müssen Sie keine PendingIntent erstellen. Ersetzen Sie den zurückgegebenen Intent durch einen JSONObject und rufen Sie getCredential() über Ihre API-Methoden auf.

    suspend fun getPasskey(
        activity: Activity,
        creationResult: JSONObject
        ): GetCredentialResponse? {
            Toast.makeText(
                activity,
                "Fetching previously stored credentials",
                Toast.LENGTH_SHORT)
                .show()
            var result: GetCredentialResponse? = null
            try {
                val request= GetCredentialRequest(
                    listOf(
                        GetPublicKeyCredentialOption(
                            creationResult.toString(),
                            null
                        ),
                        GetPasswordOption()
                    )
                )
                result = credMan.getCredential(activity, request)
                if (result.credential is PublicKeyCredential) {
                    val publicKeycredential = result.credential as PublicKeyCredential
                    Log.i("TAG", "Passkey ${publicKeycredential.authenticationResponseJson}")
                    return result
                }
            } catch (e: Exception) {
                showErrorAlert(activity, e)
            }
            return result
        }
    
  5. Sobald der Aufruf erfolgreich ist, senden Sie die Antwort zurück an den Server, um den Nutzer zu validieren und zu authentifizieren. Die Anfrage- und Antwortparameter für diesen API-Aufruf ähneln der FIDO2-Implementierung, sodass keine Änderungen erforderlich sind.

Weitere Informationen