Le Gestionnaire d'identifiants est une nouvelle API Jetpack 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) dans une seule API. Elle simplifie donc l'intégration pour les développeurs.
De plus, Credential Manager unifie l'interface de connexion pour toutes les méthodes d'authentification des utilisateurs, qui peuvent ainsi se connecter plus facilement aux applications, quelle que soit la méthode choisie.
À propos des clés d'accès
Les clés d'accès constituent une option plus sûre et plus facile à utiliser que les mots de passe. Grâce à celles-ci, les utilisateurs peuvent se connecter aux applications et aux sites Web à l'aide d'un capteur biométrique (empreinte digitale ou reconnaissance faciale, par exemple), d'un code ou d'un schéma. Il s'agit d'un moyen permettant aux utilisateurs de se connecter facilement, sans avoir à mémoriser des noms d'utilisateur ni des mots de passe.
Les clés d'accès s'appuient sur la norme WebAuthn (Web Authentication) développée conjointement par FIDO Alliance et W3C (World Wide Web Consortium). WebAuthn authentifie les utilisateurs à l'aide de la cryptographie à clé publique. Le site Web ou l'application à laquelle l'utilisateur se connecte peut voir et stocker la clé publique, mais jamais la clé privée. La clé privée reste secrète et sécurisée. Étant donné que la clé est unique et associée au site Web ou à l'application, les clés d'accès ne peuvent pas être obtenues par hameçonnage, ce qui renforce la sécurité.
Le Gestionnaire d'identifiants permet aux utilisateurs de créer des clés d'accès et de les stocker dans le Gestionnaire de mots de passe de Google.
Conditions préalables
Pour utiliser le Gestionnaire d'identifiants, suivez les étapes décrites dans cette section.
Utiliser une version récente de la plate-forme
Le Gestionnaire d'identifiants est compatible avec Android 4.4 (niveau d'API 19) et versions ultérieures.
Ajouter des dépendances à votre application
Ajoutez les dépendances suivantes au fichier build.gradle
du module de votre application :
Groovy
dependencies { implementation "androidx.credentials:credentials:1.0.0-alpha02" // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation "androidx.credentials:credentials-play-services-auth:1.0.0-alpha02" }
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.0.0-alpha02") // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation("androidx.credentials:credentials-play-services-auth:1.0.0-alpha02") }
Ajouter la prise en charge de Digital Asset Links
Afin d'activer la prise en charge des clés d'accès pour votre application Android, associez votre application à un site Web qui vous appartient. Pour déclarer cette association, procédez comme suit :
Créez un fichier JSON Digital Asset Links. Par exemple, pour déclarer que le site Web
https://signin.example.com
et une application Android ayant le nom de packagecom.example
peuvent partager les identifiants de connexion, créez un fichier nomméassetlinks.json
avec le contenu suivant :[{ "relation": ["delegate_permission/common.get_login_creds"], "target": { "namespace": "web", "site": "https://signin.example.com" } }, { "relation": ["delegate_permission/common.get_login_creds"], "target": { "namespace": "android_app", "package_name": "com.example", "sha256_cert_fingerprints": [ SHA_HEX_VALUE ] } }]
Le champ
relation
est un tableau d'une ou de plusieurs chaînes décrivant la relation déclarée. Pour déclarer que les applications et les sites partagent les identifiants de connexion, spécifiez la chaînedelegate_permission/common.get_login_creds
.Le champ
target
est un objet qui spécifie l'élément auquel la déclaration s'applique. Les champs suivants identifient un site Web :namespace
web
site
URL du site Web, au format
https://domain[:optional_port]
. (par exemple,https://www.example.com
).La propriété domain doit être complète, et optional_port doit être omis si vous utilisez le port 443 pour HTTPS.
Une cible
site
ne peut être qu'un domaine racine : vous ne pouvez pas limiter une association d'applications à un sous-répertoire spécifique. N'incluez pas de chemin d'accès dans l'URL, comme une barre oblique finale.Les sous-domaines ne sont pas considérés comme identiques : si vous spécifiez domain comme
www.example.com
, le domainewww.counter.example.com
ne sera pas associé à votre application.Les champs suivants identifient une application Android :
namespace
android_app
package_name
Nom de package déclaré dans le fichier manifeste de l'application. Exemple : com.example.android
sha256_cert_fingerprints
Empreinte SHA256 du certificat de signature de votre application. Hébergez le fichier JSON Digital Assets Links à l'emplacement suivant sur le domaine de connexion :
https://domain[:optional_port]/.well-known/assetlinks.json
Par exemple, si votre domaine de connexion est
signin.example.com
, hébergez le fichier JSON surhttps://signin.example.com/.well-known/assetlinks.json
.Le fichier MIME du fichier Digital Asset Links doit être au format JSON. Assurez-vous que le serveur envoie un en-tête
Content-Type: application/json
dans la réponse.Assurez-vous que votre hôte autorise Google à récupérer votre fichier Digital Asset Links. Si vous avez un fichier
robots.txt
, il doit permettre à l'agent Googlebot de récupérer/.well-known/assetlinks.json
. La plupart des sites peuvent simplement autoriser n'importe quel agent automatisé à récupérer les fichiers du chemin/.well-known/
afin que d'autres services puissent accéder aux métadonnées de ces fichiers :User-agent: * Allow: /.well-known/
Déclarez l'association dans l'application Android :
Ajoutez une ressource de chaîne
asset_statements
au fichierstrings.xml
. La chaîneasset_statements
est un objet JSON qui spécifie les fichiersassetlinks.json
à charger. Vous devez échapper les apostrophes et les guillemets que vous utilisez dans la chaîne. Par exemple :<string name="asset_statements" translatable="false"> [{ \"include\": \"https://signin.example.com/.well-known/assetlinks.json\" }] </string>
> GET /.well-known/assetlinks.json HTTP/1.1 > User-Agent: curl/7.35.0 > Host: signin.example.com < HTTP/1.1 200 OK < Content-Type: application/json
Configurer le Gestionnaire d'identifiants
Pour configurer et initialiser un objet CredentialManager
, ajoutez une logique semblable à celle-ci :
Kotlin
// Use your app or activity context to instantiate a client instance of // CredentialManager. val credentialManager = CredentialManager.create(context)
Java
// Use your app or activity context to instantiate a client instance of // CredentialManager. CredentialManager credentialManager = CredentialManager.create(context)
Connecter un utilisateur
Pour récupérer toutes les clés d'accès et options de mots de passe associées au compte de l'utilisateur, procédez comme suit :
Initialisez les options d'authentification avec mot de passe et clé d'accès :
Kotlin
// Retrieves the user's saved password for your app from their // password provider. val getPasswordOption = GetPasswordOption() // Get passkeys from the user's public key credential provider. val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( requestJson = requestJson, preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials )
Java
// Retrieves the user's saved password for your app from their // password provider. GetPasswordOption getPasswordOption = new GetPasswordOption(); // Get passkeys from the user's public key credential provider. GetPublicKeyCredentialOption getPublicKeyCredentialOption = new GetPublicKeyCredentialOption(requestJson, preferImmediatelyAvailableCredentials);
Créez la requête de connexion en utilisant les options récupérées à l'étape précédente :
Kotlin
val getCredRequest = GetCredentialRequest( listOf(getPasswordOption, getPublicKeyCredentialOption) )
Java
GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder() .addCredentialOption(getPasswordOption) .addCredentialOption(getPublicKeyCredentialOption) .build();
Lancez le flux de connexion :
Kotlin
coroutineScope.launch { try { val result = credentialManager.getCredential( request = getCredRequest, activity = activity, ) handleSignIn(result) } catch (e : GetCredentialException) { handleFailure(e) } } fun handleSignIn(result: GetCredentialResponse) { // Handle the successfully returned credential. val credential = result.credential when (credential) { is PublicKeyCredential -> { responseJson = credential.authenticationResponseJson fidoAuthenticateWithServer(responseJson) } is PasswordCredential -> { val username = credential.id val password = credential.password passwordAuthenticateWithServer(username, password) } else -> { // Catch any unrecognized credential type here. Log.e(TAG, "Unexpected type of credential") } } }
Java
credentialManager.getCredentialAsync( getCredRequest, activity, cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { // Handle the successfully returned credential. Credential credential = result.getCredential(); if (credential instanceof PublicKeyCredential) { String responseJson = ((PublicKeyCredential) credential) .getAuthenticationResponseJson(); fidoAuthenticateToServer(responseJson); } else if (credential instanceof PasswordCredential) { Log.d(TAG, "Got PasswordCredential"); String id = ((PasswordCredential) credential).getId(); String password = ((PasswordCredential) credential) .getPassword(); firebaseSignInWithPassword(id, password); } else { Log.e( TAG, "Unexpected type of credential: " + credential.getClass().getName()); } } @Override public void onError(GetCredentialException e) { Log.e(TAG, "Sign in failed with exception", e); } } );
L'extrait de code suivant montre comment mettre en forme la requête JSON lorsque vous obtenez une clé d'accès :
{
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
"allowCredentials": [],
"timeout": 1800000,
"userVerification": "required",
"rpId": "credential-manager-app-test.glitch.me"
}
L'extrait de code suivant présente un exemple de réponse JSON une fois que vous avez obtenu un identifiant de clé publique :
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
}
}
Flux d'inscription
Vous pouvez enregistrer un utilisateur à authentifier, soit à l'aide d'une clé d'accès, soit avec un mot de passe.
Créer une clé d'accès
Pour permettre aux utilisateurs d'enregistrer une clé d'accès et de l'utiliser pour la réauthentification, enregistrez des identifiants utilisateur à l'aide d'un objet CreatePublicKeyCredentialRequest
:
Kotlin
fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) { val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest( // Contains the request in JSON format. Uses the standard WebAuthn // web JSON spec. requestJson = requestJson, // Defines whether you prefer to use only immediately available credentials, // not hybrid credentials, to fulfill this request. This value is false // by default. preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials, ) // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. coroutineScope.launch { try { val result = credentialManager.createCredential( request = createPublicKeyCredentialRequest, activity = activity, ) handlePasskeyRegistrationResult(result) } catch (e : CreateCredentialException){ handleFailure(e) } } fun handleFailure(e: CreateCredentialException) { when (e) { is CreatePublicKeyCredentialDomException -> { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(e.domError) } is CreateCredentialCancellationException -> { // The user intentionally canceled the operation and chose not // to register the credential. } is CreateCredentialInterruptedException -> { // Retry-able error. Consider retrying the call. } is CreateCredentialProviderConfigurationException -> { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } is CreateCredentialUnknownException -> ... is CreateCustomCredentialException -> { // You have encountered an error from a 3rd-party SDK. If you // make the API call with a request object that's a subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, then you // should check for any custom exception type constants within // that SDK to match with e.type. Otherwise, drop or log the // exception. } else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}") } } }
Java
public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) { CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest = // `requestJson` contains the request in JSON format. Uses the standard // WebAuthn web JSON spec. // `preferImmediatelyAvailableCredentials` defines whether you prefer // to only use immediately available credentials, not hybrid credentials, // to fulfill this request. This value is false by default. new CreatePublicKeyCredentialRequest( requestJson, preferImmediatelyAvailableCredentials); // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. credentialManager.createCredentialAsync( createPublicKeyCredentialRequest, requireActivity(), cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { @Override public void onResult(CreateCredentialResponse result) { handleSuccessfulCreatePasskeyResult(result); } @Override public void onError(CreateCredentialException e) { if (e instanceof CreatePublicKeyCredentialDomException) { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError()); } else if (e instanceof CreateCredentialCancellationException) { // The user intentionally canceled the operation and chose not // to register the credential. } else if (e instanceof CreateCredentialInterruptedException) { // Retry-able error. Consider retrying the call. } else if (e instanceof CreateCredentialProviderConfigurationException) { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } else if (e instanceof CreateCredentialUnknownException) { } else if (e instanceof CreateCustomCredentialException) { // You have encountered an error from a 3rd-party SDK. If // you make the API call with a request object that's a // subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, // then you should check for any custom exception type // constants within that SDK to match with e.type. // Otherwise, drop or log the exception. } else { Log.w(TAG, "Unexpected exception type " + e.getClass().getName()); } } } ); }
Mettre en forme la requête JSON
Après avoir créé une clé d'accès, vous devez l'associer au compte de l'utilisateur et stocker sa clé publique sur votre serveur. L'extrait de code suivant montre comment mettre en forme la requête JSON lorsque vous créez une clé d'accès.
Cet article de blog explique comment appliquer une authentification simplifiée à vos applications et comment mettre en forme une requête JSON lorsque vous créez des clés d'accès pour l'authentification. Il explique également pourquoi les mots de passe ne constituent pas une solution d'authentification efficace, comment exploiter les identifiants biométriques existants, comment associer votre application à un site Web dont vous êtes propriétaire, comment créer des clés d'accès et comment les utiliser pour s'authentifier.
{
"challenge": "nhkQXfE59Jb97VyyNJkvDiXucMEvltduvcrDmGrODHY",
"rp": {
"name": "CredMan App Test",
"id": "credential-manager-app-test.glitch.me"
},
"user": {
"id": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0",
"name": "helloandroid@gmail.com",
"displayName": "helloandroid@gmail.com"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
Gérer la réponse JSON
L'extrait de code suivant présente un exemple de réponse JSON visant à créer des identifiants de clé publique. Découvrez comment gérer les identifiants de clé publique obtenus.
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
}
}
Enregistrer le mot de passe d'un utilisateur
Si l'utilisateur fournit un nom d'utilisateur et un mot de passe pour un flux d'authentification dans votre application, vous pouvez enregistrer des identifiants utilisateur permettant d'authentifier cet utilisateur. Pour ce faire, créez un objet CreatePasswordRequest
:
Kotlin
fun registerPassword(username: String, password: String) { // Initialize a CreatePasswordRequest object. val createPasswordRequest = CreatePasswordRequest(id = username, password = password) // Create credentials and handle result. coroutineScope.launch { try { val result = credentialManager.createCredential(createPasswordRequest) handleRegisterPasswordResult(result) } catch (e: CreateCredentialException) { handleFailure(e) } } }
Java
void registerPassword(String username, String password) { // Initialize a CreatePasswordRequest object. CreatePasswordRequest createPasswordRequest = new CreatePasswordRequest(username, password); // Register the username and password. credentialManager.createCredentialAsync( createPasswordRequest, requireActivity(), cancellationSignal, requireContext().getMainExecutor(), new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { @Override public void onResult(CreateCredentialResponse result) { handleResult(result); } @Override public void onError(CreateCredentialException e) { handleFailure(e) } } ); }
Résoudre les erreurs courantes
Le tableau suivant présente plusieurs descriptions et codes d'erreur courants, ainsi que certaines informations sur leurs causes :
Code d'erreur et description | Cause |
---|---|
"On Begin Sign In Failure: 16" : l'appelant a été temporairement bloqué en raison d'un trop grand nombre d'invites de connexion annulées. | Si vous rencontrez cette erreur imposant un délai de 24 heures avant toute nouvelle connexion pendant le développement, vous pouvez l'annuler en effaçant le stockage des applications des services Google Play. Si vous souhaitez désactiver ce délai sur un appareil de test ou un émulateur, vous pouvez également accéder à l'application Téléphone et saisir le code suivant : |
"On Begin Sign In Failure: 8" : erreur interne inconnue. |
|
"CreatePublicKeyCredentialDomException" : la requête entrante ne peut pas être validée | L'ID de package de l'application n'est pas enregistré auprès de votre serveur. Vérifiez cela dans votre intégration côté serveur. |