Migrer de FIDO2 au Gestionnaire d'identifiants

Prenant en charge les clés d'accès, la connexion fédérée et les fournisseurs d'authentification tiers, le Gestionnaire d'identifiants est l'API recommandée pour l'authentification sur Android. Elle offre un environnement sécurisé et pratique permettant aux utilisateurs de synchroniser et de gérer leurs identifiants. Les développeurs utilisant des identifiants FIDO2 locaux doivent mettre à jour leur application pour qu'elle accepte l'authentification par clé d'accès via l'intégration à l'API Gestionnaire d'identifiants. Ce document explique comment migrer votre projet de FIDO2 vers le Gestionnaire d'identifiants.

Pourquoi passer de FIDO2 au Gestionnaire d'identifiants

Dans la plupart des cas, vous devez migrer le fournisseur d'authentification de votre application Android vers le Gestionnaire d'identifiants. Vous pouvez migrer vers le Gestionnaire d'identifiants pour les raisons suivantes :

  • Prise en charge des clés d'accès : le Gestionnaire d'identifiants prend en charge les clés d'accès, un nouveau mécanisme d'authentification sans mot de passe, plus sécurisé et plus facile à utiliser que les mots de passe.
  • Plusieurs méthodes de connexion : le Gestionnaire d'identifiants prend en charge plusieurs méthodes de connexion, y compris les mots de passe, les clés d'accès et les méthodes de connexion fédérée. Cela permet aux utilisateurs de s'authentifier plus facilement auprès de votre application, quelle que soit leur méthode d'authentification préférée.
  • Prise en charge des fournisseurs d'identifiants tiers : sur Android 14 ou version ultérieure, le Gestionnaire d'identifiants prend en charge plusieurs fournisseurs d'identifiants tiers. Cela signifie que vos utilisateurs peuvent utiliser leurs identifiants existants d'autres fournisseurs pour se connecter à votre application.
  • Expérience utilisateur cohérente : le Gestionnaire d'identifiants offre une expérience utilisateur plus cohérente pour l'authentification entre les applications et les mécanismes de connexion. Cela permet aux utilisateurs de comprendre et d'utiliser plus facilement le flux d'authentification de votre application.

Pour commencer la migration de FIDO2 vers le Gestionnaire d'identifiants, suivez la procédure ci-dessous.

Mettre à jour les dépendances

  1. Mettez à jour le plug-in Kotlin dans le fichier build.gradle de votre projet vers la version 1.8.10 ou une version ultérieure.

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. Dans le fichier build.gradle de votre projet, mettez à jour vos dépendances pour utiliser le Gestionnaire d'identifiants et l'Authentification des Services Play.

    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. Remplacez l'initialisation FIDO par l'initialisation du Gestionnaire d'identifiants. Ajoutez cette déclaration dans la classe que vous utilisez pour les méthodes de création de clés d'accès et de connexion :

    val credMan = CredentialManager.create(context)
    

Créer des clés d'accès

Vous devez créer une clé d'accès, l'associer au compte d'un utilisateur et stocker la clé publique de la clé d'accès sur votre serveur pour que l'utilisateur puisse s'en servir pour se connecter. Configurez votre application avec cette fonctionnalité en mettant à jour les appels de fonction d'enregistrement.

Figure 1 : Cette figure montre comment les données sont échangées entre l'application et le serveur lorsqu'une clé d'accès est créée à l'aide du Gestionnaire d'identifiants.
  1. Pour obtenir les paramètres nécessaires envoyés à la méthode createCredential() lors de la création de la clé d'accès, ajoutez name("residentKey").value("required") (comme décrit dans la spécification WebAuthn) à votre appel du serveur registerRequest().

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .method("POST", jsonRequestBody {
            name("attestation").value("none")
            name("authenticatorSelection").objectValue {
                name("residentKey").value("required")
            }
        }).build()
        // ...
    }
    
  2. Définissez le type return sur JSONObject pour registerRequest() et toutes les fonctions enfants.

    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. Supprimez en toute sécurité toutes les méthodes de votre vue qui gèrent le lanceur d'intents et les appels de résultats d'activité.

  4. Étant donné que registerRequest() renvoie désormais un JSONObject, vous n'avez pas besoin de créer un PendingIntent. Remplacez l'intent renvoyé par un JSONObject. Mettez à jour vos appels de lanceur d'intents pour appeler createCredential() à partir de l'API Gestionnaire d'identifiants. Appelez la méthode de l'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. Une fois l'appel réussi, renvoyez la réponse au serveur. La requête et la réponse de cet appel sont semblables à l'implémentation FIDO2. Aucune modification n'est donc requise.

S'authentifier avec des clés d'accès

Après avoir configuré la création de clés d'accès, vous pouvez configurer votre application pour permettre aux utilisateurs de se connecter et de s'authentifier à l'aide de leurs clés d'accès. Pour ce faire, vous devez mettre à jour votre code d'authentification pour gérer les résultats du Gestionnaire d'identifiants et implémenter une fonction permettant l'authentification via des clés d'accès.

Figure 2 : Flux d'authentification par clé d'accès du Gestionnaire d'identifiants.
  1. L'appel de requête de connexion au serveur pour envoyer les informations nécessaires à la requête getCredential() est identique à l'implémentation de FIDO2. Aucune modification n'est requise.
  2. Comme pour l'appel de requête d'enregistrement, la réponse renvoyée est au 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. Supprimez en toute sécurité toutes les méthodes de votre vue qui gèrent le lanceur d'intents et les appels de résultats d'activité.

  4. Étant donné que signInRequest() renvoie désormais un JSONObject, vous n'avez pas besoin de créer un PendingIntent. Remplacez l'intent renvoyé par un JSONObject, puis appelez getCredential() à partir de vos méthodes d'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. Une fois l'appel réussi, renvoyez la réponse au serveur pour valider et authentifier l'utilisateur. Les paramètres de requête et de réponse de cet appel d'API sont semblables à l'implémentation FIDO2. Aucune modification n'est donc requise.

Ressources supplémentaires