1. Introduction
Qu'est-ce que l'API FIDO2 ?
L'API FIDO2 permet aux applications Android de créer et d'utiliser des identifiants basés sur des clés publiques fiables et certifiées afin d'authentifier les utilisateurs. L'API fournit une implémentation du client WebAuthn, qui prend en charge l'utilisation d'authentificateurs itinérants BLE, NFC et USB (clés de sécurité), ainsi qu'un authentificateur de plate-forme, qui permet à l'utilisateur de s'authentifier à l'aide de son empreinte digitale ou du verrouillage de son écran.
Objectifs de l'atelier...
Dans cet atelier de programmation, vous allez créer une application Android avec une fonctionnalité de réauthentification simple à l'aide d'un lecteur d'empreinte digitale. "Réauthentification" se produit lorsqu'un utilisateur se connecte à une application, puis s'authentifie à nouveau lorsqu'il revient à votre application ou lorsqu'il tente d'accéder à une section importante de votre application. Ce second cas est également appelé "authentification étape par étape".
Ce que vous allez apprendre...
Vous apprendrez à appeler l'API Android FIDO2 et découvrirez les options à votre disposition pour répondre à différents besoins. Vous découvrirez également les bonnes pratiques spécifiques à la réauthentification.
Ce dont vous avez besoin...
- Appareil Android équipé d'un lecteur d'empreinte digitale (même sans lecteur d'empreinte digitale, le verrouillage de l'écran peut fournir une fonctionnalité de validation utilisateur équivalente)
- Android OS 7.0 ou versions ultérieures avec les dernières mises à jour. Veillez à enregistrer une empreinte digitale ou un verrouillage de l'écran.
2. Configuration
Cloner le dépôt
Consultez le dépôt GitHub.
https://github.com/android/codelab-fido2
$ git clone https://github.com/android/codelab-fido2.git
Que allons-nous implémenter ?
- Autoriser les utilisateurs à enregistrer un "authentificateur de plate-forme de validation des utilisateurs" (le téléphone Android équipé d'un lecteur d'empreinte digitale servira comme tel).
- Autorisez les utilisateurs à s'authentifier de nouveau à l'aide de leur empreinte digitale.
Pour afficher un aperçu de ce que vous allez compiler, cliquez ici.
Démarrer le projet d'atelier de programmation
Une fois terminée, l'application envoie des requêtes à un serveur à l'adresse https://webauthn-codelab.glitch.me. Vous pouvez essayer la version Web de la même application sur cette page.
Vous allez travailler sur votre propre version de l'application.
- Accédez à la page de modification du site Web à l'adresse https://glitch.com/edit/#!/webauthn-codelab.
- Recherchez "Remixer pour modifier". dans le coin supérieur droit. En appuyant sur le bouton, vous pouvez "dupliquer" le code et continuez avec votre propre version avec une nouvelle URL de projet.
- Copiez le nom du projet en haut à gauche (vous pouvez le modifier si vous le souhaitez).
- Collez-le dans la section
HOSTNAME
du fichier.env
dans le glitch.
3. Associer votre application et un site Web à Digital Asset Links
Pour utiliser l'API FIDO2 sur une application Android, associez-la à un site Web et partagez les identifiants entre les deux. Pour ce faire, utilisez Digital Asset Links. Vous pouvez déclarer des associations en hébergeant un fichier JSON Digital Asset Links sur votre site Web et en ajoutant un lien vers ce fichier dans le fichier manifeste de votre application.
Héberger .well-known/assetlinks.json
sur votre domaine
Vous pouvez définir une association entre votre application et le site Web en créant un fichier JSON et en le plaçant à .well-known/assetlinks.json
. Heureusement, nous avons un code de serveur qui affiche automatiquement le fichier assetlinks.json
, simplement en ajoutant les paramètres d'environnement suivants au fichier .env
dans un glitch:
ANDROID_PACKAGENAME
: nom du package de votre application (com.example.android.fido2)ANDROID_SHA256HASH
: hachage SHA256 de votre certificat de signature
Pour obtenir le hachage SHA256 de votre certificat de signature du développeur, utilisez la commande ci-dessous. Le mot de passe par défaut du keystore de débogage est "android".
$ keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore
En accédant à https://<your-project-name>.glitch.me/.well-known/assetlinks.json
, vous devriez obtenir une chaîne JSON semblable à celle-ci:
[{
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "web",
"site": "https://<your-project-name>.glitch.me"
}
}, {
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "com.example.android.fido2",
"sha256_cert_fingerprints": ["DE:AD:BE:EF:..."]
}
}]
Ouvrir le projet dans Android Studio
Cliquez sur "Ouvrir un projet Android Studio existant". sur l'écran d'accueil d'Android Studio.
Choisissez la version "Android" dans le dépôt "dépôt".
Associer l'application à votre remix
Ouvrir le fichier gradle.properties
. En bas du fichier, remplacez l'URL de l'hôte par le remix Glitch que vous venez de créer.
// ...
# The URL of the server
host=https://<your-project-name>.glitch.me
À ce stade, votre configuration Digital Asset Links devrait être prête.
4. Découvrez le fonctionnement de l'application
Commençons par vérifier comment l'application fonctionne maintenant. Veillez à sélectionner "app-start" dans la liste déroulante de la configuration d'exécution. Cliquez sur "Exécuter". (le triangle vert à côté de la liste déroulante) pour lancer l'application sur votre appareil Android connecté.
Lorsque vous lancez l'application, un écran s'affiche pour vous permettre de saisir votre nom d'utilisateur. Il s'agit de la radio UsernameFragment
. À des fins de démonstration, l'application et le serveur acceptent n'importe quel nom d'utilisateur. Il vous suffit de taper quelque chose et d'appuyer sur « Suivant ».
L'écran suivant est AuthFragment
. C'est ici que l'utilisateur peut se connecter avec un mot de passe. Plus tard, nous ajouterons ici une fonctionnalité de connexion avec FIDO2. Encore une fois, pour les besoins de la démonstration, l'application et le serveur acceptent n'importe quel mot de passe. Il vous suffit de saisir votre texte et d'appuyer sur "Connexion".
Voici le dernier écran de cette appli, HomeFragment
. Pour l'instant, vous ne voyez ici qu'une liste d'identifiants vide. Appuyer sur "Réauthentification" vous ramène à AuthFragment
. Appuyer sur "Se déconnecter" vous ramène à UsernameFragment
. Le bouton d'action flottant avec "+" ne fait rien pour l'instant, mais lance l'enregistrement d'un
un nouvel identifiant une fois que vous avez implémenté le flux d’enregistrement FIDO2.
Avant de commencer à coder, voici une technique utile. Dans Android Studio, appuyez sur TODO. en bas. La liste de toutes les tâches à effectuer de cet atelier de programmation s'affiche. Nous allons commencer par le premier TODO dans la section suivante.
5. Enregistrer un identifiant à l'aide d'une empreinte digitale
Pour activer l'authentification à l'aide d'une empreinte digitale, vous devez d'abord enregistrer un identifiant généré par un authentificateur de plate-forme de validation de l'utilisateur (un authentificateur intégré à l'appareil qui valide l'utilisateur à l'aide de données biométriques, comme un lecteur d'empreinte digitale, par exemple).
Comme nous l'avons vu dans la section précédente, le bouton d'action flottant ne sert plus à rien. Voyons comment enregistrer un nouvel identifiant.
Appelez l'API du serveur: /auth/registerRequest
Ouvrez AuthRepository.kt
et recherchez TODO(1).
Ici, registerRequest
est la méthode appelée lorsque l'utilisateur appuie sur le bouton d'action flottant. Nous aimerions que cette méthode appelle l'API du serveur /auth/registerRequest
. L'API renvoie un ApiResult
avec tous les PublicKeyCredentialCreationOptions
dont le client a besoin pour générer de nouveaux identifiants.
Nous pouvons ensuite appeler getRegisterPendingIntent
avec les options. Cette API FIDO2 renvoie un PendingIntent Android pour ouvrir une boîte de dialogue d'empreinte digitale et générer un nouvel identifiant. Nous pouvons renvoyer ce PendingIntent à l'appelant.
La méthode se présente alors comme suit :
suspend fun registerRequest(): PendingIntent? {
fido2ApiClient?.let { client ->
try {
val sessionId = dataStore.read(SESSION_ID)!!
when (val apiResult = api.registerRequest(sessionId)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
if (apiResult.sessionId != null) {
dataStore.edit { prefs ->
prefs[SESSION_ID] = apiResult.sessionId
}
}
val task = client.getRegisterPendingIntent(apiResult.data)
return task.await()
}
}
} catch (e: Exception) {
Log.e(TAG, "Cannot call registerRequest", e)
}
}
return null
}
Ouvrir la boîte de dialogue d'enregistrement de l'empreinte
Ouvrez HomeFragment.kt
et recherchez TODO(2).
C'est là que l'UI récupère l'intent à partir de AuthRepository
. Ici, nous allons utiliser le membre createCredentialIntentLauncher
pour lancer le PendingIntent obtenu à l'étape précédente. Une boîte de dialogue s'ouvre pour la génération d'identifiants.
binding.add.setOnClickListener {
lifecycleScope.launch {
val intent = viewModel.registerRequest()
if (intent != null) {
createCredentialIntentLauncher.launch(
IntentSenderRequest.Builder(intent).build()
)
}
}
}
Recevoir ActivityResult avec le nouvel identifiant
Ouvrez HomeFragment.kt
et recherchez TODO(3).
Cette méthode handleCreateCredentialResult
est appelée après la fermeture de la boîte de dialogue de l'empreinte. Si un identifiant a bien été généré, le membre data
d'ActivityResult contient les informations d'identification.
Tout d'abord, nous devons extraire un PublicKeyCredential du data
. L'intent de données comporte un champ supplémentaire de tableau d'octets avec la clé Fido.FIDO2_KEY_CREDENTIAL_EXTRA
. Dans PublicKeyCredential
, vous pouvez utiliser une méthode statique appelée deserializeFromBytes
pour transformer le tableau d'octets en un objet PublicKeyCredential
.
Vérifiez ensuite si le membre response
de cet objet d'identification est un AuthenticationErrorResponse
. Si c'est le cas, cela signifie qu'une erreur s'est produite lors de la génération des identifiants. sinon nous pouvons envoyer les identifiants à notre backend.
Une fois terminée, la méthode se présente comme suit:
private fun handleCreateCredentialResult(activityResult: ActivityResult) {
val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
when {
activityResult.resultCode != Activity.RESULT_OK ->
Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_LONG).show()
bytes == null ->
Toast.makeText(requireContext(), R.string.credential_error, Toast.LENGTH_LONG)
.show()
else -> {
val credential = PublicKeyCredential.deserializeFromBytes(bytes)
val response = credential.response
if (response is AuthenticatorErrorResponse) {
Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_LONG)
.show()
} else {
viewModel.registerResponse(credential)
}
}
}
}
Appelez l'API du serveur: /auth/registerResponse
Ouvrez AuthRepository.kt
et recherchez TODO(4).
Cette méthode registerResponse
est appelée une fois que l'UI a généré un nouvel identifiant et que nous voulons le renvoyer au serveur.
L'objet PublicKeyCredential
contient des informations sur les nouveaux identifiants générés. Nous voulons maintenant mémoriser l'ID de notre clé locale afin de la distinguer des autres clés enregistrées sur le serveur. Dans l'objet PublicKeyCredential
, prenez sa propriété rawId
et placez-la dans une variable de chaîne locale à l'aide de toBase64
.
Nous sommes maintenant prêts à envoyer les informations au serveur. Utilisez api.registerResponse
pour appeler l'API du serveur et renvoyer la réponse. La valeur renvoyée contient la liste de tous les identifiants enregistrés sur le serveur, y compris le nouveau.
Enfin, nous pouvons enregistrer les résultats dans DataStore
. La liste des identifiants doit être enregistrée avec la clé CREDENTIALS
en tant que StringSet
. Vous pouvez utiliser toStringSet
pour convertir la liste d'identifiants en StringSet
.
De plus, nous enregistrons l'identifiant avec la clé LOCAL_CREDENTIAL_ID
.
suspend fun registerResponse(credential: PublicKeyCredential) {
try {
val sessionId = dataStore.read(SESSION_ID)!!
val credentialId = credential.rawId.toBase64()
when (val result = api.registerResponse(sessionId, credential)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
dataStore.edit { prefs ->
result.sessionId?.let { prefs[SESSION_ID] = it }
prefs[CREDENTIALS] = result.data.toStringSet()
prefs[LOCAL_CREDENTIAL_ID] = credentialId
}
}
}
} catch (e: ApiException) {
Log.e(TAG, "Cannot call registerResponse", e)
}
}
Exécutez l'application. Vous pourrez cliquer sur le bouton d'action flottant et enregistrer un nouvel identifiant.
6. Authentifier l'utilisateur avec une empreinte
Un identifiant est désormais enregistré sur l'application et sur le serveur. Nous pouvons maintenant l'utiliser pour permettre à l'utilisateur de se connecter. Nous ajoutons une fonctionnalité de connexion par empreinte digitale à AuthFragment
. Lorsqu'un utilisateur y a accès, une boîte de dialogue d'empreinte digitale s'affiche. Une fois l'authentification effectuée, l'utilisateur est redirigé vers HomeFragment
.
Appelez l'API du serveur: /auth/signinRequest
Ouvrez AuthRepository.kt
et recherchez TODO(5).
Cette méthode signinRequest
est appelée lorsque AuthFragment
est ouvert. Ici, nous voulons demander le serveur et voir si nous pouvons laisser l'utilisateur se connecter avec FIDO2.
Nous devons d'abord récupérer PublicKeyCredentialRequestOptions
sur le serveur. Utilisez api.signInRequest
pour appeler l'API du serveur. Le ApiResult
renvoyé contient PublicKeyCredentialRequestOptions
.
Avec PublicKeyCredentialRequestOptions
, nous pouvons utiliser l'API FIDO2 getSignIntent
pour créer un PendingIntent afin d'ouvrir la boîte de dialogue de l'empreinte.
Enfin, nous pouvons renvoyer le PendingIntent à l'UI.
suspend fun signinRequest(): PendingIntent? {
fido2ApiClient?.let { client ->
val sessionId = dataStore.read(SESSION_ID)!!
val credentialId = dataStore.read(LOCAL_CREDENTIAL_ID)
if (credentialId != null) {
when (val apiResult = api.signinRequest(sessionId, credentialId)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
val task = client.getSignPendingIntent(apiResult.data)
return task.await()
}
}
}
}
return null
}
Ouvrir la boîte de dialogue d'empreinte digitale pour l'assertion
Ouvrez AuthFragment.kt
et recherchez TODO(6).
C'est à peu près la même chose que pour l'enregistrement. Nous pouvons lancer la boîte de dialogue d'empreinte digitale avec le membre signIntentLauncher
.
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
launch {
viewModel.signinRequests.collect { intent ->
signIntentLauncher.launch(
IntentSenderRequest.Builder(intent).build()
)
}
}
launch {
...
}
}
Gérer l'ActivityResult
Ouvrez AuthFragment.kt et recherchez TODO(7).
Là encore, c'est la même chose que pour l'enregistrement. Nous pouvons extraire PublicKeyCredential
, rechercher une erreur et la transmettre à ViewModel.
private fun handleSignResult(activityResult: ActivityResult) {
val bytes = activityResult.data?.getByteArrayExtra(Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
when {
activityResult.resultCode != Activity.RESULT_OK ->
Toast.makeText(requireContext(), R.string.cancelled, Toast.LENGTH_SHORT).show()
bytes == null ->
Toast.makeText(requireContext(), R.string.auth_error, Toast.LENGTH_SHORT)
.show()
else -> {
val credential = PublicKeyCredential.deserializeFromBytes(bytes)
val response = credential.response
if (response is AuthenticatorErrorResponse) {
Toast.makeText(requireContext(), response.errorMessage, Toast.LENGTH_SHORT)
.show()
} else {
viewModel.signinResponse(credential)
}
}
}
}
Appelez l'API du serveur: /auth/signinResponse
Ouvrez AuthRepository.kt
et recherchez TODO(8).
L'objet PublicKeyCredential comporte un ID d'identification sous la forme keyHandle
. Comme pour le flux d'inscription, enregistrons-la dans une variable de chaîne locale afin de pouvoir la stocker ultérieurement.
Nous sommes maintenant prêts à appeler l'API du serveur avec api.signinResponse
. La valeur renvoyée contient une liste d'identifiants.
La connexion est maintenant établie. Nous devons stocker tous les résultats dans notre DataStore
. La liste des identifiants doit être stockée sous forme de StringSet avec la clé CREDENTIALS
. L'ID local que nous avons enregistré ci-dessus doit être stocké sous forme de chaîne avec la clé LOCAL_CREDENTIAL_ID
.
Enfin, nous devons mettre à jour l'état de connexion afin que l'UI puisse rediriger l'utilisateur vers HomeFragment. Pour ce faire, émettez un objet SignInState.SignedIn
au SharedFlow nommé signInStateMutable
. Nous voulons également appeler refreshCredentials
pour récupérer les identifiants de l'utilisateur afin qu'ils soient listés dans l'interface utilisateur.
suspend fun signinResponse(credential: PublicKeyCredential) {
try {
val username = dataStore.read(USERNAME)!!
val sessionId = dataStore.read(SESSION_ID)!!
val credentialId = credential.rawId.toBase64()
when (val result = api.signinResponse(sessionId, credential)) {
ApiResult.SignedOutFromServer -> forceSignOut()
is ApiResult.Success -> {
dataStore.edit { prefs ->
result.sessionId?.let { prefs[SESSION_ID] = it }
prefs[CREDENTIALS] = result.data.toStringSet()
prefs[LOCAL_CREDENTIAL_ID] = credentialId
}
signInStateMutable.emit(SignInState.SignedIn(username))
refreshCredentials()
}
}
} catch (e: ApiException) {
Log.e(TAG, "Cannot call registerResponse", e)
}
}
Exécutez l'application et cliquez sur "Reauth" (Réauthentifier). pour ouvrir AuthFragment
. Vous devriez maintenant voir une boîte de dialogue vous invitant à vous connecter avec votre empreinte digitale.
Félicitations ! Vous savez maintenant utiliser l'API FIDO2 sur Android pour l'enregistrement et la connexion.
7. Félicitations !
Vous avez terminé l'atelier de programmation Votre première API Android FIDO2.
Connaissances acquises
- Enregistrer un identifiant à l'aide d'un authentificateur de plate-forme de validation des utilisateurs
- Authentifier un utilisateur à l'aide d'un authentificateur enregistré
- Options disponibles pour enregistrer un nouvel authentificateur.
- Bonnes pratiques concernant l'expérience utilisateur pour la réauthentification à l'aide d'un capteur biométrique.
Étape suivante
- Découvrez comment créer une expérience similaire sur un site Web.
Pour l'apprendre, suivez le premier atelier de programmation WebAuthn.
Ressources
Un grand merci à Yuriy Ackermann de FIDO Alliance pour votre aide.