Intégrer le Gestionnaire d'identifiants à la solution de votre fournisseur d'identifiants

Le Gestionnaire d'identifiants fait référence à un ensemble d'API introduites dans Android 14 et offrant plusieurs méthodes de connexion, dont la combinaison nom d'utilisateur/mot de passe, les clés d'accès et les solutions fédérées (par exemple, Se connecter avec Google). Lorsque l'API Gestionnaire d'identifiants est appelée, le système Android regroupe les identifiants de tous les fournisseurs d'identifiants installés sur l'appareil. Ce document décrit l'ensemble des API qui fournissent des points de terminaison d'intégration pour ces fournisseurs d'identifiants.

Configuration

Avant d'implémenter une fonctionnalité dans votre fournisseur d'identifiants, suivez la procédure de configuration décrite dans les sections suivantes.

Déclarer des dépendances

Dans le fichier build.gradle de votre module, déclarez une dépendance à l'aide de la dernière version de la bibliothèque du Gestionnaire d'identifiants :

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

Déclarer un élément de service dans le fichier manifeste

Dans le fichier manifeste AndroidManifest.xml de votre application, ajoutez une déclaration <service> pour une classe de service qui étend la classe CredentialProviderService à partir de la bibliothèque androidx.credentials, comme dans l'exemple ci-dessous.

<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>

L'autorisation et le filtre d'intent présentés ci-dessus sont essentiels pour que le flux du Gestionnaire d'identifiants fonctionne comme prévu. L'autorisation est obligatoire pour que seul le système Android puisse être lié à ce service. Le filtre d'intent permet au Gestionnaire d'identifiants de reconnaître ce service en tant que fournisseur d'identifiants.

Déclarer les types d'identifiants compatibles

Dans le répertoire res/xml, créez un fichier nommé provider.xml. Dans ce fichier, déclarez les types d'identifiants pris en charge par votre service. Pour ce faire, utilisez des constantes définies pour chaque type d'identifiant dans la bibliothèque. Dans l'exemple suivant, le service accepte les mots de passe traditionnels ainsi que les clés d'accès, pour lesquels des constantes sont définies en tant que TYPE_PASSWORD_CREDENTIAL et TYPE_PUBLIC_KEY_CREDENTIAL :

<?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>

Pour ce qui est des niveaux d'API précédents, les fournisseurs d'identifiants s'intègrent à des API, par exemple avec la saisie automatique pour les mots de passe et d'autres données. Ces fournisseurs peuvent utiliser la même infrastructure interne pour stocker les types d'identifiants existants, tout en l'étendant aux autres types compatibles, y compris les clés d'accès.

Approche en deux phases pour les interactions avec les fournisseurs

Le Gestionnaire d'identifiants interagit avec les fournisseurs d'identifiants en deux phases :

  1. La première phase est la phase de début/de requête au cours de laquelle le système se lie aux services du fournisseur d'identifiants et appelle les méthodes onBeginGetCredentialRequest(), onBeginCreateCredentialRequest() ou onClearCredentialStateRequest() avec des requêtes Begin…. Les fournisseurs doivent traiter ces requêtes et répondre avec des réponses Begin…, en les renseignant avec des entrées représentant des options visuelles à afficher dans le sélecteur de compte. PendingIntent doit être défini pour chaque entrée.
  2. Une fois que l'utilisateur a sélectionné une entrée, la phase de sélection commence et le PendingIntent associé à l'entrée est déclenché, ce qui génère l'activité du fournisseur correspondant. Une fois que l'utilisateur a terminé d'interagir avec cette activité, le fournisseur d'identifiants doit définir la réponse sur le résultat de l'activité avant de l'achever. Cette réponse est ensuite envoyée à l'application cliente qui a appelé le Gestionnaire d'identifiants.

Gérer la création de clés d'accès

Gérer les requêtes pour la création de clés d'accès

Lorsqu'une application cliente souhaite créer une clé d'accès et la stocker avec un fournisseur d'identifiants, elle appelle l'API createCredential. Pour gérer cette requête dans votre service de fournisseur d'identifiants et faire en sorte que la clé d'accès soit effectivement stockée dans votre espace de stockage, procédez comme indiqué dans les sections suivantes.

  1. Remplacez la méthode onBeginCreateCredentialRequest() dans votre service à la suite de CredentialProviderService.
  2. Gérez BeginCreateCredentialRequest en créant un BeginCreateCredentialResponse correspondant et en le transmettant via le rappel.
  3. Lorsque vous créez BeginCreateCredentialResponse, ajoutez les CreateEntries nécessaires. Chaque CreateEntry doit correspondre à un compte dans lequel les identifiants peuvent être enregistrés. PendingIntent doit également être défini avec les autres métadonnées obligatoires.

L'exemple suivant montre comment mettre en œuvre ces étapes.

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
        )
    )
}

La création de PendingIntent doit respecter les directives suivantes :

  • L'activité correspondante doit être configurée pour afficher toute invite, confirmation ou sélection biométrique nécessaire
  • Toutes les données requises dont le fournisseur a besoin lorsque l'activité correspondante est appelée doivent être définies en tant que données supplémentaires sur l'intent utilisé pour créer PendingIntent, par exemple accountId dans le flux de création.
  • PendingIntent doit être créé avec l'indicateur PendingIntent.FLAG_MUTABLE pour que le système puisse ajouter la requête finale à l'élément supplémentaire sur l'intent.
  • PendingIntent ne doit pas être créé avec l'indicateur PendingIntent.FLAG_ONE_SHOT, car l'utilisateur peut sélectionner une entrée, revenir en arrière et la sélectionner à nouveau, ce qui déclencherait PendingIntent deux fois.
  • PendingIntent doit être créé avec un code de requête unique afin que chaque entrée puisse disposer de son propre PendingIntent correspondant.

Gérer la sélection des entrées pour les requêtes de création de clés d'accès

  1. Lorsque l'utilisateur sélectionne un CreateEntry renseigné précédemment, le PendingIntent correspondant est appelé et l'Activity associée du fournisseur est créée.
  2. Une fois la méthode onCreate de votre activité appelée, accédez à l'intent associé et transmettez-le à la classe PendingIntentHander pour obtenir ProviderCreateCredentialRequest.
  3. Extrayez requestJson, callingAppInfo et clientDataHash de la requête.
  4. Extrayez le accountId local de l'élément supplémentaire sur l'intent. Il s'agit d'un exemple d'implémentation propre à une application et n'est donc pas obligatoire. Cet ID de compte peut être utilisé pour stocker cet identifiant sur cet ID de compte spécifique
  5. Validez requestJson. L'exemple ci-dessous utilise des classes de données locales comme PublicKeyCredentialCreationOptions pour convertir l'entrée JSON en classe structurée conformément à la spécification WebAuthn. En tant que fournisseur d'identifiants, vous pouvez le remplacer par votre propre analyseur.
  6. Vérifiez le lien de l'élément pour l'application à l'origine de l'appel si celui-ci provient d'une application Android native.
  7. Affichez une invite d'authentification. L'exemple ci-dessous utilise l'API biométrique d'Android.
  8. Une fois l'authentification effectuée, générez credentialId et une paire de clés.
  9. Enregistrez la clé privée dans votre base de données locale pour callingAppInfo.packageName.
  10. Créez une réponse JSON de l'API Web Authentication comprenant la clé publique et credentialId. L'exemple ci-dessous utilise des classes utilitaires locales telles que AuthenticatorAttestationResponse et FidoPublicKeyCredential pour vous aider à créer un fichier JSON basé sur les spécifications mentionnées précédemment. En tant que fournisseur d'identifiants, vous pouvez remplacer ces classes par vos propres compilateurs.
  11. Créez CreatePublicKeyCredentialResponse avec le fichier JSON généré ci-dessus.
  12. Définissez CreatePublicKeyCredentialResponse en tant qu'activité supplémentaire sur un Intent via PendingIntentHander.setCreateCredentialResponse() et définissez cet intent sur le résultat de l'activité.
  13. Terminez l'activité.

L'exemple de code ci-dessous illustre ces étapes. Ce code doit être géré dans la classe d'activité après l'appel de la méthode onCreate().

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)}"
}

Gérer les requêtes pour la création de mots de passe

Afin de gérer les requêtes pour la création de mots de passe, procédez comme suit :

  • Dans la méthode processCreateCredentialRequest() mentionnée dans la section précédente, ajoutez un autre boîtier dans le bloc du contacteur pour la gestion des requêtes de mots de passe.
  • Lorsque vous créez BeginCreateCredentialResponse, ajoutez les CreateEntries nécessaires.
  • Chaque CreateEntry doit correspondre à un compte dans lesquels les identifiants peuvent être enregistrés. Un PendingIntent doit être défini avec d'autres métadonnées.

L'exemple suivant montre comment mettre en œuvre ces étapes :

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)
}

Gérer la sélection des entrées pour les requêtes de création de mots de passe

Lorsque l'utilisateur sélectionne un élément CreateEntry renseigné, le PendingIntent correspondant s'exécute et affiche l'activité associée. Accédez à l'intent associé transmis dans onCreate et transmettez-le à la classe PendingIntentHander pour obtenir la méthode ProviderCreateCredentialRequest.

L'exemple ci-dessous montre comment mettre en œuvre ce processus. Ce code doit être géré dans la méthode onCreate() de votre activité.

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()

Gérer la connexion des utilisateurs

Pour gérer la connexion des utilisateurs, procédez comme suit :

  • Lorsqu'une application cliente tente de connecter un utilisateur, elle prépare une instance GetCredentialRequest.
  • Le framework d'Android propage cette requête à tous les fournisseurs d'identifiants applicables en l'associant à ces services.
  • Le service du fournisseur reçoit ensuite un BeginGetCredentialRequest contenant une liste de BeginGetCredentialOption, chacun contenant des paramètres permettant de récupérer les identifiants correspondants.

Pour gérer cette requête dans votre service de fournisseur d'identifiants, procédez comme suit :

  1. Remplacez la méthode onBeginGetCredentialRequest() pour gérer la requête. Si vos identifiants sont verrouillés, vous pouvez immédiatement définir un AuthenticationAction sur la réponse et appeler le rappel.

    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())
        }
    }
    

    Les fournisseurs qui doivent déverrouiller les identifiants avant de renvoyer des credentialEntries doivent configurer un intent en attente qui redirige l'utilisateur vers le flux de déverrouillage de l'application :

    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. Récupérez les identifiants de votre base de données locale et configurez-les à l'aide de CredentialEntries pour les afficher dans le sélecteur. Pour les clés d'accès, vous pouvez définir credentialId comme élément supplémentaire sur l'intent afin de savoir à quel identifiant il est mappé lorsque l'utilisateur sélectionne cette entrée.

    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. Interrogez les identifiants à partir de votre base de données, créez des entrées de clés d'accès et de mots de passe à renseigner.

    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. Après avoir interrogé et renseigné les identifiants, vous devez gérer la phase de sélection pour les identifiants sélectionnés par l'utilisateur, qu'il s'agisse d'une clé d'accès ou d'un mot de passe.

Gérer la sélection des utilisateurs pour les clés d'accès

  1. Dans la méthode onCreate de l'activité correspondante, récupérez l'intent associé et transmettez à PendingIntentHandler.retrieveProviderGetCredentialRequest().
  2. Extrayez GetPublicKeyCredentialOption de la requête récupérée ci-dessus. Vous devez ensuite extraire requestJson et clientDataHash de cette option.
  3. Extrayez credentialId de l'élément supplémentaire sur l'intent renseigné par le fournisseur d'identifiants lors de la configuration du PendingIntent correspondant.
  4. Extrayez la clé d'accès de votre base de données locale à l'aide des paramètres de requête consultés ci-dessus.
  5. Déclarez que la clé d'accès est valide avec les métadonnées extraites et la vérification de l'utilisateur.

    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. Pour valider l'utilisateur, affichez une invite biométrique (ou toute autre méthode d'assertion). L'extrait de code ci-dessous utilise l'API biométrique d'Android.

  7. Une fois l'authentification effectuée, créez une réponse JSON basée sur les spécifications d'assertion de l'authentification Web W3. Dans l'extrait de code ci-dessous, les classes de données d'aide comme AuthenticatorAssertionResponse sont utilisées pour intégrer les paramètres structurés et les convertir au format JSON requis. La réponse contient une signature numérique issue de la clé privée d'un identifiant WebAuthn. Le serveur d'un tiers de confiance peut vérifier cette signature pour authentifier un utilisateur avant qu'il ne se connecte.

  8. Créez un PublicKeyCredential à l'aide du fichier JSON généré ci-dessus et définissez-le sur une réponse GetCredentialResponse finale. Définissez cette réponse finale sur le résultat de cette activité.

L'exemple suivant montre comment mettre en œuvre ces étapes :

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)

Gérer la sélection des utilisateurs pour l'authentification par mot de passe

  1. Dans l'activité correspondante, accédez à l'intent transmis à onCreate et extrayez ProviderGetCredentialRequest à l'aide de PendingIntentHandler.
  2. Utilisez GetPasswordOption dans la requête pour récupérer les identifiants du mot de passe pour le nom du package entrant.

    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. Une fois les identifiants récupérés, définissez la réponse pour les identifiants du mot de passe sélectionnés.

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

Gérer la sélection d'une entrée d'action d'authentification

Comme indiqué précédemment, un fournisseur d'identifiants peut définir une action AuthenticationAction si les identifiants sont verrouillés. Si l'utilisateur sélectionne cette entrée, l'activité correspondant à l'action d'intent définie dans PendingIntent est appelée. Les fournisseurs d'identifiants peuvent ensuite afficher un flux d'authentification biométrique ou un mécanisme similaire pour déverrouiller les identifiants. En cas de réussite, le fournisseur d'identifiants doit créer une réponse BeginGetCredentialResponse, comme c'est le cas pour la gestion de la connexion des utilisateurs décrite ci-dessous, car les identifiants sont désormais débloqués. Cette réponse doit ensuite être définie via la méthode PendingIntentHandler.setBeginGetCredentialResponse() avant que l'intent préparé ne soit défini comme résultat et que l'activité ne soit terminée.

Effacer les requêtes d'identifiants

Une application cliente peut demander à ce que tout état conservé pour la sélection des identifiants soit effacé. Par exemple, un fournisseur d'identifiants peut se souvenir des identifiants précédemment sélectionnés et ne les renvoyer que la prochaine fois. Une application cliente appelle cette API et s'attend à ce que la sélection persistante soit effacée. Votre service de fournisseur d'identifiants peut gérer cette requête en ignorant la méthode onClearCredentialStateRequest() :

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

Obtenir une liste d'autorisation d'applications privilégiées

Les applications privilégiées telles que les navigateurs Web effectuent des appels du Gestionnaire d'identifiants pour le compte d'autres parties de confiance en définissant le paramètre origin dans les méthodes GetCredentialRequest() et CreatePublicKeyCredentialRequest() du Gestionnaire d'identifiants. Pour traiter ces requêtes, le fournisseur d'identifiants récupère l'origin à l'aide de l'API getOrigin().

Pour récupérer l'origin, l'application du fournisseur d'identifiants doit transmettre une liste d'appelants privilégiés et approuvés à l'API androidx.credentials.provider.CallingAppInfo's getOrigin(). Cette liste d'autorisation doit être un objet JSON valide. Le origin est renvoyé si packageName et les empreintes du certificat obtenues à partir de signingInfo correspondent à celles d'une application trouvée dans le privilegedAllowlist transmis à l'API getOrigin(). Une fois la valeur origin obtenue, l'application du fournisseur doit considérer cet appel privilégié et définir ce origin sur les données client dans AuthenticatorResponse, au lieu de calculer le origin à l'aide de la signature de l'application appelante.

Si vous récupérez un origin, utilisez le clientDataHash fourni directement dans CreatePublicKeyCredentialRequest() ou GetPublicKeyCredentialOption() au lieu de l'assemblage et du hachage de clientDataJSON lors de la requête de signature. Pour éviter les problèmes d'analyse JSON, définissez une valeur d'espace réservé pour clientDataJSON dans la réponse d'attestation et d'assertion.

Le Gestionnaire de mots de passe de Google utilise une liste d'autorisation ouverte pour les appels vers getOrigin(). En tant que fournisseur d'identifiants, vous pouvez utiliser cette liste ou fournir la vôtre au format JSON décrit par l'API. C'est au fournisseur de sélectionner la liste à utiliser. Pour obtenir un accès privilégié avec des fournisseurs d'identifiants tiers, reportez-vous à la documentation fournie par le tiers.

Activer les fournisseurs sur un appareil

Les utilisateurs doivent activer le fournisseur sur leur appareil en se rendant dans Paramètres > Mots de passe et comptes > Votre fournisseur > Activer ou Désactiver.

Sur Android 14 ou version ultérieure, appelez l'API createSettingsPendingIntent() pour renvoyer un intent en attente. Lorsqu'elle est appelée, elle affiche également un écran qui permet à un utilisateur d'activer votre Gestionnaire d'identifiants.

fun createSettingsPendingIntent(): PendingIntent