Implémenter la restauration des identifiants avec Credential Manager

Cette page explique comment créer une clé de restauration, comment l'utiliser pour se connecter et comment la supprimer.

Compatibilité des versions

La fonctionnalité Restore Credentials de Credential Manager fonctionne sur les appareils équipés d'Android 9 (niveau d'API 28) ou version ultérieure, de la version principale 242200000 (v24.22) ou ultérieure des services Google Play (GMS) et de la version 1.5.0 ou ultérieure de la bibliothèque androidx.credentials. Sur les appareils sans services Google Play ou avec une ancienne version de GMS, interceptez CreateCredentialProviderConfigurationException et GetCredentialProviderConfigurationException pour que votre application revienne correctement à la connexion standard.

Prérequis

Configurez un serveur de tiers de confiance WebAuthn semblable au serveur pour les clés d'accès. Si vous avez déjà configuré un serveur pour gérer l'authentification avec des clés d'accès, vous pouvez réutiliser les mêmes points de terminaison d'enregistrement et d'assertion WebAuthn pour les clés de restauration.

Dépendances

Ajoutez les dépendances suivantes au fichier build.gradle du module de votre application :

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha02"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
}

Restore Credentials est disponible à partir de la version 1.5.0 de la bibliothèque androidx.credentials. Toutefois, il est recommandé d'utiliser les dernières versions stables des dépendances dans la mesure du possible.

Impact de android:allowBackup sur la restauration des identifiants

L'attribut de fichier manifeste android:allowBackup contrôle la sauvegarde des données de l'application, qui interagit avec Restore Credentials de trois manières différentes :

  • Transfert de clé de restauration (indépendant de allowBackup) : les services Google Play transfèrent la paire de clés RestoreCredential à l'aide d'une sauvegarde cloud chiffrée de bout en bout ou d'un transfert local d'appareil à appareil (D2D), que android:allowBackup soit défini sur true ou false. Pour les transferts d'appareil à appareil, la clé de restauration est toujours transférée. La seule condition dans laquelle elle n'est pas transférée est lors d'une sauvegarde cloud lorsque l'utilisateur a désactivé la sauvegarde cloud dans les paramètres Android.
  • Restauration en arrière-plan dans BackupAgent (nécessite android:allowBackup="true") : la connexion silencieuse en arrière-plan avant que l'utilisateur n'ouvre l'application sur le nouvel appareil (BackupAgent.onRestoreFinished()) ne s'exécute que lorsque android:allowBackup="true" et android:backupAgent sont configurés dans votre fichier manifeste. Si la valeur est android:allowBackup="false", restaurez la session lorsque l'utilisateur lance votre Activity principal pour la première fois. Vous pouvez également mettre en œuvre les deux approches pour rendre la restauration plus robuste.
  • Tests de sauvegarde cloud Android Studio (nécessite android:allowBackup="true") : pour tester le type de sauvegarde Cloud dans l'outil de sauvegarde et de restauration d'Android Studio, vous devez disposer de android:allowBackup="true", tandis que les tests Appareil à appareil fonctionnent indépendamment de allowBackup.

Présentation

L'API Restore Credentials suit un cycle de vie en trois étapes sur l'ancien et le nouvel appareil de l'utilisateur :

  1. Créer une clé de restauration sur l'appareil précédent : une fois que l'utilisateur s'est connecté (ou au démarrage de l'application s'il est déjà connecté et qu'aucune clé de restauration n'est enregistrée dans l'espace de stockage local), récupérez les options de création à partir de votre serveur de tiers de confiance et appelez credentialManager.createCredential().
  2. Se connecter avec la clé de restauration sur le nouvel appareil : récupérez la clé RestoreCredential transférée à l'aide d'une stratégie à deux niveaux : d'abord en arrière-plan dans BackupAgent.onRestoreFinished() (lorsque android:allowBackup="true") pour que la synchronisation en arrière-plan et les notifications push reprennent immédiatement, puis en tant que solution de secours dans votre lanceur d'applications Activity.onCreate() lors du premier lancement de l'application.
  3. Supprimer la clé de restauration lors de la déconnexion : lorsque l'utilisateur se déconnecte ou que sa session est invalidée par le serveur, appelez credentialManager.clearCredentialState() avec ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) et effacez votre indicateur de synchronisation locale.

Créer une clé de restauration

Votre application doit couvrir tous les cas de connexion d'un utilisateur pour s'assurer que les utilisateurs actifs disposent d'une clé de restauration. Créez la clé de restauration dans les scénarios suivants :

  • Si l'utilisateur est connecté et qu'une clé de restauration n'a pas encore été créée (par exemple, dans la méthode onCreate() pour l'Activity principal).
  • Lorsque l'utilisateur se connecte ou termine le processus d'enregistrement d'un nouveau compte.

Pour éviter d'appeler createCredential() à chaque lancement de l'application, stockez un indicateur de synchronisation locale tel que has_synced_restore_credential dans l'espace de stockage local. Ne stockez pas cette option dans les fichiers SharedPreferences ou DataStore sauvegardés par défaut, sauf si ce fichier est exclu dans data_extraction_rules.xml. Sinon, la sauvegarde automatique copie has_synced_restore_credential = true sur le nouvel appareil et l'empêche de générer ou de faire pivoter sa propre clé de restauration.

Instancier Credential Manager

Utilisez le contexte d'activité de votre application pour instancier un objet CredentialManager.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

Obtenir les options de création d'identifiants à partir du serveur de votre application

Utilisez une bibliothèque compatible avec FIDO sur votre serveur d'application pour envoyer à votre application cliente le fichier JSON PublicKeyCredentialCreationOptions requis pour créer l'identifiant de restauration. Pour en savoir plus sur l'implémentation côté serveur, consultez Conseils côté serveur.

Le fichier JSON des options de création suit la même structure que l'enregistrement de la clé d'accès :

{
  "challenge": "<base64url-encoded challenge>",
  "rp": {
    "name": "<relying party name>",
    "id": "<relying party host name>"
  },
  "user": {
    "id": "<base64url-encoded user ID>",
    "name": "<user name>",
    "displayName": "<user display name>"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "attestation": "none",
  "excludeCredentials": [
    {
        "id": "<base64url-encoded credential ID to exclude>", 
        "type": "public-key"
    }
  ],
  "authenticatorSelection": {
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Assurez-vous que le fichier JSON des options de création répond aux exigences suivantes :

  • challenge et user.id : encodez les deux valeurs sous forme de chaînes Base64URL sans marge (en remplaçant + par -, / par _ et en supprimant le caractère = de fin). Les caractères Base64 standards ou les ID d'entiers bruts entraînent une erreur CreateRestoreCredentialDomException ou IllegalArgumentException.
  • rp.id : définissez cette valeur sur le domaine de votre partie de confiance (par exemple, example.com).
  • pubKeyCredParams : incluez {"type": "public-key", "alg": -7} (ES256) et {"type": "public-key", "alg": -257} (RS256).

Créer la clé de restauration

Après avoir reçu le fichier JSON des options de création de clé publique de votre serveur, encapsulez-le dans un objet CreateRestoreCredentialRequest et appelez createCredential() sur l'objet CredentialManager.

// createRestoreRequest contains the details sent by the server 
val response = credentialManager.createCredential(context, createRestoreRequest)

Points clés concernant le code

  • L'objet CreateRestoreCredentialRequest contient les champs suivants :

    • requestJson : options de création d'identifiants envoyées par le serveur d'applications au format de l'API Web Authentication pour PublicKeyCredentialCreationOptionsJSON.
    • isCloudBackupEnabled : champ Boolean permettant de déterminer si la clé de restauration doit être sauvegardée dans le cloud. Par défaut, cette option est définie sur true. Ce champ accepte les valeurs suivantes :

      • true : (recommandé) cette valeur permet de sauvegarder les clés de restauration dans le cloud si l'utilisateur a activé la sauvegarde Google et le chiffrement de bout en bout, comme un verrouillage de l'écran.
      • false : cette valeur enregistre la clé localement et non dans le cloud. La clé n'est pas disponible sur le nouvel appareil si l'utilisateur choisit de restaurer les données depuis le cloud, mais elle est toujours transférée lors d'une configuration locale d'appareil à appareil (D2D) par câble ou Wi-Fi.

Gérer la réponse de création d'identifiants

L'API Credential Manager renvoie une réponse de type CreateRestoreCredentialResponse. Cette réponse contient la réponse d'enregistrement des identifiants de clé publique au format JSON.

Envoyez response.registrationResponseJson de votre application à votre serveur de tiers de confiance pour valider et stocker la clé publique avec le compte de l'utilisateur, puis enregistrez votre repère de synchronisation locale dans l'espace de stockage local. Pour en savoir plus sur la validation côté serveur, consultez les conseils pour les clés d'accès.

Lors du processus de création de la clé de restauration, gérez les exceptions suivantes :

  • CreateRestoreCredentialDomException : cette exception se produit si requestJson n'est pas valide et ne respecte pas le format WebAuthn pour PublicKeyCredentialCreationOptionsJSON.
  • E2eeUnavailableException : cette exception se produit si isCloudBackupEnabled est défini sur true, mais que l'appareil de l'utilisateur ne dispose pas de sauvegarde des données ni de chiffrement de bout en bout, comme un verrouillage de l'écran.
    Pour vous assurer que les identifiants de restauration sont créés pour les transferts locaux d'appareil à appareil (D2D) même lorsque le chiffrement de bout en bout dans le cloud n'est pas disponible, interceptez E2eeUnavailableException et réessayez immédiatement createCredential() avec CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false).
  • IllegalArgumentException : cette exception se produit si requestJson est vide, n'est pas un JSON valide ou ne comporte pas de user.id valide encodé en Base64URL conforme aux spécifications WebAuthn.

Se connecter avec une clé de restauration

Utilisez l'API Restore Credentials pour connecter l'utilisateur de manière silencieuse pendant le processus de configuration de l'appareil.

Obtenir les options de récupération des identifiants auprès du serveur d'application

Avant d'appeler getCredential() sur le nouvel appareil, demandez une question d'authentification JSON WebAuthn PublicKeyCredentialRequestOptions à votre serveur d'application. Étant donné que l'application sur le nouvel appareil ne sait pas encore quel utilisateur se connecte, ce point de terminaison doit être non authentifié (découverte sans utilisateur). Pour obtenir des conseils sur le serveur, consultez Se connecter avec une clé d'accès et le guide d'authentification côté serveur.

Le fichier JSON des options de récupération présente la structure suivante :

{
  "challenge": "<your app challenge>",
  "allowCredentials": [],
  "rpId": "<your app server domain>"
}

Configurez le fichier JSON des options de récupération sur votre serveur comme suit :

  • allowCredentials : omettez ce champ ou transmettez un tableau vide ([]), car le compte de l'utilisateur n'est pas connu avant la restauration. Une fois que getCredential() est renvoyé, votre serveur identifie l'utilisateur à partir du champ response.userHandle (qui correspond au user.id enregistré lors de la création) et recherche la clé publique par id (credentialId).
  • userVerification : défini sur "discouraged" (ou "preferred"). Étant donné que la restauration des identifiants s'exécute de manière silencieuse sans interface utilisateur biométrique ou de verrouillage de l'écran, Android remplace "required" par "discouraged", et les données de l'authentificateur renvoyées à votre serveur ont l'indicateur "Utilisateur validé" (uv) défini sur false. Configurez le vérificateur de votre serveur WebAuthn pour qu'il accepte uv = false pour les assertions de clé de restauration.

Obtenir la clé de restauration

Pour obtenir la clé de restauration sur le nouvel appareil, encapsulez le requestJson du serveur dans un GetRestoreCredentialOption à l'intérieur d'un GetCredentialRequest et appelez getCredential() sur l'objet CredentialManager.

Mettez en œuvre une stratégie de restauration à deux niveaux afin que les utilisateurs soient connectés, que la restauration des données de l'application en arrière-plan soit exécutée ou non :

  • Niveau 1 (restauration en arrière-plan dans BackupAgent.onRestoreFinished()) : si la sauvegarde des données de l'application est activée (android:allowBackup="true"), sous-classez BackupAgent et remplacez onRestoreFinished() (n'utilisez pas onRestore(), qui n'est appelé que pour les anciennes sauvegardes clé-valeur). Comme onRestoreFinished() est synchrone et que getCredential() est une fonction suspend Kotlin, reliez l'appel à l'aide de runBlocking(Dispatchers.IO). Interceptez NoCredentialException de manière silencieuse lorsqu'aucune clé de restauration n'est présente.
  • Niveau 2 (solution de secours au premier plan dans le lanceur d'applications Activity.onCreate()) : lors du premier lancement de l'application, lorsqu'aucune session locale n'existe, appelez getCredential() depuis lifecycleScope avant d'afficher l'écran de connexion. Cela gère les applications avec android:allowBackup="false" et récupère les données si l'appareil était hors connexion lors de l'exécution de onRestoreFinished().

Si vous ajoutez un nouveau BackupAgent à une application qui utilise déjà la sauvegarde automatique avec android:allowBackup="true", définissez également android:fullBackupOnly="true" sur l'élément <application> dans AndroidManifest.xml afin qu'Android continue d'utiliser la sauvegarde automatique au lieu de passer à l'ancienne sauvegarde clé-valeur.

// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()

// Create the GetRestoreCredentialRequest object
val options = GetRestoreCredentialOption(authenticationJson)
val getRequest = GetCredentialRequest(listOf(options))

val response = credentialManager.getCredential(context, getRequest)

// Type-check and extract the restore credential
val credential = response.credential as RestoreCredential

L'API Credential Manager renvoie un GetCredentialResponse dont la propriété credential est un RestoreCredential (et non un PublicKeyCredential) contenant authenticationResponseJson.

Gérer la réponse de connexion

Envoyez restoreCredential.authenticationResponseJson de l'application à votre serveur de tiers de confiance pour valider l'assertion cryptographique et émettre un nouveau jeton de session pour l'utilisateur. Pour en savoir plus sur la validation côté serveur, consultez Se connecter avec une clé d'accès.

Lorsque vous concevez votre stockage de backend et vos règles de cycle de vie pour les clés de restauration, suivez ces consignes :

  • Différenciez les clés de restauration des clés d'accès utilisateur : ajoutez un tag aux clés de restauration dans votre base de données afin qu'elles soient masquées sur les écrans de gestion des clés d'accès visibles par l'utilisateur et qu'elles ne soient pas soumises aux vérifications de l'utilisateur (uv = true).
  • Prise en charge de plusieurs appareils actifs par utilisateur : un utilisateur peut posséder plusieurs appareils (par exemple, un téléphone et une tablette). Autorisez plusieurs clés de restauration actives par compte utilisateur (par exemple, par appareil ou par ID d'installation) au lieu d'écraser une seule ligne.
  • Définissez une valeur TTL appropriée et nettoyez les clés orphelines : la désinstallation d'une application ou l'effacement des données de l'application supprime la clé de restauration locale sans en informer votre serveur. Conservez les clés de restauration suffisamment longtemps pour couvrir les périodes de mise à niveau de l'appareil (même si l'utilisateur se déconnecte de l'ancien appareil après le transfert) et supprimez les clés obsolètes en fonction de la valeur TTL d'inactivité ou des limites de clés par utilisateur.

Supprimer la clé de restauration

Credential Manager est sans état et n'est pas au courant de l'activité de l'utilisateur. Il ne supprime donc pas automatiquement les clés de restauration après utilisation. Pour supprimer une clé de restauration, appelez la méthode clearCredentialState(). Pour des raisons de sécurité, supprimez la clé chaque fois qu'un utilisateur se déconnecte afin qu'il ne soit pas reconnecté de manière silencieuse au prochain lancement de l'application.

Les clés de restauration ne sont supprimées que dans les cas suivants :

  • Actions au niveau du système : les utilisateurs désinstallent l'application ou effacent ses données.
  • Appels au niveau de l'application : supprimez la clé de manière programmatique en appelant clearCredentialState() lorsque vous gérez la déconnexion de l'utilisateur dans le code de votre application.

Étant donné qu'un ClearCredentialStateRequest() par défaut n'efface que l'état de session standard et laisse la clé de restauration intacte, effectuez un double effacement lors de la déconnexion :

  1. Appelez credentialManager.clearCredentialState(ClearCredentialStateRequest()) pour effacer l'état de la session du fournisseur d'identifiants actif.
  2. Appelez credentialManager.clearCredentialState() avec ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) pour supprimer le RestoreCredential stocké.
  3. Supprimez votre repère has_synced_restore_credential local de l'espace de stockage local.
// Create a ClearCredentialStateRequest object
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)

// When the user logs out, delete the restore key
val response = credentialManager.clearCredentialState(clearRequest)

Étapes suivantes