Esegui la migrazione da FIDO2 a Gestore delle credenziali

Grazie al supporto di passkey, accessi federati e provider di autenticazione di terze parti, Credential Manager è l'API consigliata per l'autenticazione su Android, che offre un ambiente sicuro e pratico che consente agli utenti di sincronizzare e gestire le proprie credenziali. Per gli sviluppatori che utilizzano le credenziali FIDO2 locali, devi aggiornare l'app per supportare l'autenticazione tramite passkey tramite l'integrazione con l'API Credential Manager. Questo documento descrive come eseguire la migrazione del progetto da FIDO2 a Gestore delle credenziali.

Motivi per eseguire la migrazione da FIDO2 a Gestore delle credenziali

Nella maggior parte dei casi, devi eseguire la migrazione del provider di autenticazione della tua app Android a Gestore delle credenziali. I motivi per eseguire la migrazione a Gestore delle credenziali includono:

  • Supporto delle passkey: Gestore delle credenziali supporta le passkey, un nuovo meccanismo di autenticazione senza password più sicuro e facile da usare delle password.
  • Più metodi di accesso: Gestore delle credenziali supporta più metodi di accesso, tra cui password, passkey e metodi di accesso federato. In questo modo, gli utenti possono eseguire l'autenticazione più facilmente nella tua app, indipendentemente dal metodo di autenticazione preferito.
  • Supporto dei provider di credenziali di terze parti: su Android 14 e versioni successive, Gestore delle credenziali supporta più provider di credenziali di terze parti. Ciò significa che gli utenti possono utilizzare le proprie credenziali esistenti di altri provider per accedere alla tua app.
  • Esperienza utente coerente: Gestore delle credenziali offre un'esperienza utente più coerente per l'autenticazione tra app e meccanismi di accesso. In questo modo, per gli utenti è più facile comprendere e utilizzare il flusso di autenticazione dell'app.

Per avviare la migrazione da FIDO2 a Gestore delle credenziali, segui i passaggi riportati di seguito.

Aggiorna dipendenze

  1. Aggiorna il plug-in Kotlin nel file build.gradle del tuo progetto alla versione 1.8.10 o successiva.

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. Nella build.gradle del progetto, aggiorna le dipendenze per utilizzare l'autenticazione di Gestore delle credenziali e Play Services.

    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. Sostituisci l'inizializzazione di FIDO con l'inizializzazione di Gestore delle credenziali. Aggiungi questa dichiarazione nel corso che utilizzi per la creazione di passkey e i metodi di accesso:

    val credMan = CredentialManager.create(context)
    

Crea passkey

Dovrai creare una nuova passkey, associarla all'account di un utente e archiviare la chiave pubblica della passkey sul tuo server prima che l'utente possa accedere con la passkey. Configura la tua app con questa funzionalità aggiornando le chiamate delle funzioni di registrazione.

Figura 1. Questa figura mostra come i dati vengono scambiati tra l'app e il server quando viene creata una passkey con Gestore delle credenziali.
  1. Per ottenere i parametri necessari da inviare al metodo createCredential() durante la creazione della passkey, aggiungi name("residentKey").value("required") come descritto nella specifica WebAuthn) alla tua chiamata server registerRequest().

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .method("POST", jsonRequestBody {
            name("attestation").value("none")
            name("authenticatorSelection").objectValue {
                name("residentKey").value("required")
            }
        }).build()
        // ...
    }
    
  2. Imposta il tipo return per registerRequest() e tutte le funzioni figlio su JSONObject.

    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. Rimuovi in sicurezza dalla tua visualizzazione tutti i metodi che gestiscono l'Avvio app di intent e le chiamate dei risultati delle attività.

  4. Poiché ora registerRequest() restituisce JSONObject, non è necessario creare un PendingIntent. Sostituisci l'intent restituito con un JSONObject. Aggiorna le chiamate dell'Avvio app di intent per chiamare createCredential() dall'API Credential Manager. Chiama il metodo API createCredential().

    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. Una volta che la chiamata ha esito positivo, invia la risposta al server. La richiesta e la risposta per questa chiamata sono simili all'implementazione FIDO2, quindi non sono necessarie modifiche.

Autenticazione con passkey

Dopo aver configurato la creazione di una passkey, puoi configurare la tua app in modo che gli utenti possano accedere e autenticarsi utilizzando le loro passkey. A tale scopo, aggiornerai il codice di autenticazione per gestire i risultati di Gestore delle credenziali e implementerai una funzione per l'autenticazione tramite passkey.

Figura 2. Flusso di autenticazione tramite passkey di Gestore delle credenziali.
  1. La chiamata di richiesta di accesso al server per ottenere le informazioni necessarie da inviare alla richiesta getCredential() corrisponde all'implementazione FIDO2. Non sono necessarie modifiche.
  2. Analogamente alla chiamata di richiesta di registrazione, la risposta restituita è in formato 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. Rimuovi in sicurezza dalla tua visualizzazione tutti i metodi che gestiscono Avvio app di intent e chiamate risultato attività.

  4. Poiché ora signInRequest() restituisce un JSONObject, non è necessario creare un PendingIntent. Sostituisci l'intent restituito con JSONObject e chiama getCredential() dai tuoi metodi API.

    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. Una volta riuscita la chiamata, invia la risposta al server per convalidare e autenticare l'utente. I parametri di richiesta e risposta per questa chiamata API sono simili all'implementazione FIDO2, quindi non sono necessarie modifiche.

Risorse aggiuntive