1. Avant de commencer
L'emploi de clés d'accès constitue une excellente alternative aux mots de passe pour renforcer la sécurité des comptes utilisateur sur les sites Web, tout en rendant ces comptes plus faciles à utiliser. Avec une clé d'accès, l'utilisateur peut se connecter à un site Web ou à une application par le biais d'une fonctionnalité de verrouillage de l'écran de l'appareil (par exemple, empreinte digitale, reconnaissance faciale ou code PIN). Vous devez créer une clé d'accès, l'associer à un compte utilisateur et stocker sa clé publique sur un serveur afin qu'un utilisateur puisse s'en servir pour se connecter.
Dans cet atelier de programmation, vous allez transformer une procédure de connexion standard de type formulaire (avec nom d'utilisateur et mot de passe) en une procédure qui prend en charge les clés d'accès et inclut les éléments suivants :
- Bouton permettant de créer une clé d'accès une fois l'utilisateur connecté
- UI affichant une liste des clés d'accès enregistrées
- Formulaire de connexion (existant) permettant aux utilisateurs de se connecter avec une clé d'accès enregistrée via le remplissage automatique de formulaire
Prérequis
- Connaissances de base sur JavaScript
- Connaissances de base sur les clés d'accès
- Connaissances de base sur l'API Web Authentication (WebAuthn)
Points abordés
- Créer une clé d'accès
- Authentifier les utilisateurs avec une clé d'accès
- Permettre à un formulaire de suggérer une clé d'accès comme option de connexion
Ce dont vous avez besoin
L'une des combinaisons "appareil-logiciel" suivantes :
- Google Chrome avec un appareil Android exécutant Android 9 ou une version ultérieure, de préférence avec un capteur biométrique
- Chrome avec un appareil Windows exécutant Windows 10 ou une version ultérieure
- Safari 16 ou version ultérieure avec un iPhone exécutant iOS 16 ou une version ultérieure, ou avec un iPad exécutant iPadOS 16 ou une version ultérieure
- Safari 16 ou version ultérieure ou Chrome, avec un ordinateur de bureau Apple exécutant macOS Ventura ou une version ultérieure
2. Configuration
Dans cet atelier de programmation, vous allez utiliser le service Glitch qui vous permet de modifier le code côté client et côté serveur avec JavaScript, et de le déployer exclusivement à partir du navigateur.
Ouvrir le projet
- Ouvrez le projet dans Glitch.
- Cliquez sur Remix (Remixer) pour dupliquer le projet Glitch.
- Dans le menu de navigation en bas de Glitch, cliquez sur Preview > Preview in a new window (Aperçu > Prévisualiser dans une nouvelle fenêtre). Un autre onglet s'ouvre dans votre navigateur.
Examiner l'état initial du site Web
- Dans l'onglet d'aperçu, saisissez un nom d'utilisateur aléatoire, puis cliquez sur Next (Suivant).
- Saisissez un mot de passe aléatoire, puis cliquez sur Sign-in (Connexion). Le mot de passe est ignoré, mais vous êtes tout de même authentifié et accédez la page d'accueil.
- Vous pouvez modifier votre nom à afficher si vous le souhaitez. C'est la seule action que vous pouvez effectuer à l'état initial.
- Cliquez sur Sign out (Déconnexion).
Dans cet état, les utilisateurs doivent saisir un mot de passe chaque fois qu'ils se connectent. Vous devez ajouter la prise en charge des clés d'accès à ce formulaire afin que les utilisateurs puissent se connecter à l'aide de la fonctionnalité de verrouillage de l'écran de leur appareil. Vous pouvez essayer l'état final à l'adresse https://passkeys-codelab.glitch.me/.
Pour en savoir plus sur le fonctionnement des clés d'accès, consultez Comment fonctionnent les clés d'accès.
3. Ajouter la possibilité de créer une clé d'accès
Pour permettre aux utilisateurs de s'authentifier avec une clé d'accès, vous devez leur donner la possibilité de créer et d'enregistrer une clé d'accès, puis de stocker sa clé publique sur le serveur.
Vous souhaitez autoriser la création d'une clé d'accès une fois l'utilisateur connecté via un mot de passe, et ajouter une UI qui lui permet de créer une clé d'accès et de consulter la liste de toutes les clés d'accès enregistrées sur la page /home
. Dans la section suivante, vous allez développer une fonction permettant de créer et d'enregistrer une clé d'accès.
Créer la fonction registerCredential()
- Dans Glitch, accédez au fichier
public/client.js
, puis faites-le défiler jusqu'en bas. - Après le commentaire approprié, ajoutez la fonction
registerCredential()
suivante :
public/client.js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to create a passkey: Create a credential.
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
};
Cette fonction crée une clé d'accès et l'enregistre sur le serveur.
Récupérer la question d'authentification et d'autres options auprès du point de terminaison du serveur
Pour qu'il soit possible de créer une clé d'accès, vous devez demander au serveur les paramètres à transmettre à WebAuthn, y compris une question d'authentification. WebAuthn est une API de navigateur qui permet à un utilisateur de créer une clé d'accès et de s'en servir pour s'authentifier. Par chance, vous disposez déjà d'un point de terminaison de serveur qui fournit ces paramètres dans cet atelier de programmation.
- Pour récupérer la question d'authentification et d'autres options auprès du point de terminaison du serveur, ajoutez le code suivant au corps de la fonction
registerCredential()
après le commentaire approprié :
public/client.js
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/registerRequest');
L'extrait de code suivant inclut des exemples des options que vous recevez du serveur :
{
challenge: *****,
rp: {
id: "example.com",
},
user: {
id: *****,
name: "john78",
displayName: "John",
},
pubKeyCredParams: [{
alg: -7, type: "public-key"
},{
alg: -257, type: "public-key"
}],
excludeCredentials: [{
id: *****,
type: 'public-key',
transports: ['internal', 'hybrid'],
}],
authenticatorSelection: {
authenticatorAttachment: "platform",
requireResidentKey: true,
}
}
La spécification WebAuthn n'inclut pas le protocole d'échange entre un serveur et un client. Cependant, le serveur de cet atelier de programmation est conçu pour renvoyer un fichier JSON aussi semblable que possible au dictionnaire PublicKeyCredentialCreationOptions
transmis à l'API WebAuthn navigator.credentials.create()
.
Le tableau suivant n'est pas exhaustif, mais contient les principaux paramètres du dictionnaire PublicKeyCredentialCreationOptions
:
Paramètres | Descriptions |
Question d'authentification générée par le serveur dans un objet | |
Identifiant unique d'un utilisateur. Cette valeur doit être un objet | |
Ce champ doit contenir un identifiant unique pour le compte, reconnaissable par l'utilisateur, comme son adresse e-mail ou son nom d'utilisateur. Il s'affiche dans le sélecteur de compte. (Pour un nom d'utilisateur, spécifiez la même valeur que pour l'authentification par mot de passe.) | |
Ce champ facultatif permet de spécifier un nom convivial pour le compte. Il ne doit pas nécessairement être unique. Il peut s'agir d'un nom choisi par l'utilisateur. Si vous ne disposez pas d'une valeur appropriée à inclure dans ce champ pour votre site Web, transmettez une chaîne vide. Ce nom peut s'afficher dans le sélecteur de compte selon le navigateur. | |
Un ID de partie de confiance (RP) est un domaine. Un site Web peut spécifier son propre domaine ou un suffixe enregistrable. Par exemple, si l'origine d'une RP est https://login.example.com:1337, l'ID de RP peut être | |
Ce champ indique les algorithmes de clé publique acceptés par la RP. Nous vous recommandons de le définir sur | |
Fournit une liste des identifiants déjà enregistrés pour empêcher l'enregistrement du même appareil à deux reprises. Si ce paramètre est fourni, le membre | |
Définissez ce paramètre sur une valeur | |
Définissez ce paramètre sur une valeur booléenne | |
Définissez la valeur sur |
Créer un identifiant
- Dans le corps de la fonction
registerCredential()
, après le commentaire approprié, reconvertissez certains paramètres encodés en Base64URL en binaires, plus précisément les chaînesuser.id
etchallenge
, ainsi que les instances de la chaîneid
incluses dans le tableauexcludeCredentials
:
public/client.js
// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);
if (options.excludeCredentials) {
for (let cred of options.excludeCredentials) {
cred.id = base64url.decode(cred.id);
}
}
- Sur la ligne suivante, définissez
authenticatorSelection.authenticatorAttachment
sur"platform"
etauthenticatorSelection.requireResidentKey
surtrue
. Ainsi, seule l'utilisation d'un authentificateur de plate-forme (l'appareil lui-même) avec une fonctionnalité d'identification détectable est autorisée.
public/client.js
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
- Sur la ligne suivante, appelez la méthode
navigator.credentials.create()
pour créer un identifiant.
public/client.js
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
Avec cet appel, le navigateur tente de valider l'identité de l'utilisateur à l'aide du verrouillage de l'écran de l'appareil.
Enregistrer l'identifiant sur le point de terminaison du serveur
Une fois que l'utilisateur a validé son identité, une clé d'accès est créée et stockée. Le site Web reçoit un objet d'identification qui contient une clé publique que vous pouvez envoyer au serveur pour enregistrer la clé d'accès.
L'extrait de code suivant contient un exemple d'objet d'identification :
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"attestationObject": *****,
"transports": ["internal", "hybrid"]
},
"authenticatorAttachment": "platform"
}
Le tableau suivant n'est pas exhaustif, mais contient les principaux paramètres de l'objet PublicKeyCredential
:
Paramètres | Descriptions |
ID de la clé d'accès créée, encodé en Base64URL. Cet identifiant aide le navigateur à déterminer si une clé d'accès correspondante est enregistrée sur l'appareil au moment de l'authentification. Cette valeur doit être stockée dans la base de données sur le backend. | |
Version de l'objet | |
Données client encodées sous la forme d'un objet | |
Objet d'attestation encodé au format | |
Liste des transports compatibles avec l'appareil. | |
Renvoie |
Pour envoyer l'objet d'identification au serveur, procédez comme suit :
- Encodez les paramètres binaires de l'identifiant en Base64URL afin qu'ils puissent être envoyés au serveur sous forme de chaîne :
public/client.js
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
credential.authenticatorAttachment = cred.authenticatorAttachment;
}
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject = base64url.encode(cred.response.attestationObject);
// Obtain transports.
const transports = cred.response.getTransports ? cred.response.getTransports() : [];
credential.response = {
clientDataJSON,
attestationObject,
transports
};
- Sur la ligne suivante, envoyez l'objet au serveur :
public/client.js
return await _fetch('/auth/registerResponse', credential);
Lorsque vous exécutez le programme, le serveur renvoie HTTP code 200
, ce qui indique que l'identifiant est enregistré.
Vous disposez maintenant de la fonction registerCredential()
complète.
Examiner le code de solution pour cette section
public/client.js
// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {
// TODO: Add an ability to create a passkey: Obtain the challenge and other options from server endpoint.
const options = await _fetch('/auth/registerRequest');
// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);
if (options.excludeCredentials) {
for (let cred of options.excludeCredentials) {
cred.id = base64url.decode(cred.id);
}
}
// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
authenticatorAttachment: 'platform',
requireResidentKey: true
}
// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
publicKey: options,
});
// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
credential.authenticatorAttachment = cred.authenticatorAttachment;
}
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject =
base64url.encode(cred.response.attestationObject);
// Obtain transports.
const transports = cred.response.getTransports ?
cred.response.getTransports() : [];
credential.response = {
clientDataJSON,
attestationObject,
transports
};
return await _fetch('/auth/registerResponse', credential);
};
4. Créer une UI pour enregistrer et gérer les identifiants de type clé d'accès
Maintenant que la fonction registerCredential()
est disponible, vous avez besoin d'un bouton pour l'appeler. Vous devez également afficher la liste des clés d'accès enregistrées.
Ajouter un espace réservé HTML
- Dans Glitch, accédez au fichier
views/home.html
. - Après le commentaire approprié, ajoutez un espace réservé pour l'UI qui affiche un bouton permettant d'enregistrer une clé d'accès et une liste de clés d'accès :
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3 class="mdc-typography mdc-typography--headline6"> Your registered
passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>
L'élément div#list
est l'espace réservé pour la liste.
Vérifier si les clés d'accès sont acceptées
Pour n'afficher l'option permettant de créer une clé d'accès que pour les utilisateurs dont les appareils sont compatibles avec les clés d'accès, vous devez d'abord vérifier si WebAuthn est disponible. Si tel est le cas, vous devez ensuite supprimer la classe hidden
pour afficher le bouton Create a passkey (Créer une clé d'accès).
Pour vérifier si un environnement est compatible avec les clés d'accès, procédez comme suit :
- À la fin du fichier
views/home.html
, après le commentaire approprié, écrivez une instruction conditionnelle qui s'exécute si la valeur dewindow.PublicKeyCredential
,PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable
etPublicKeyCredential.isConditionalMediationAvailable
esttrue
.
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
- Dans le corps de l'instruction conditionnelle, vérifiez si l'appareil peut créer une clé d'accès, puis si la clé d'accès peut être suggérée pour le remplissage automatique de formulaire.
views/home.html
try {
const results = await Promise.all([
// Is platform authenticator available in this browser?
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
// Is conditional UI available in this browser?
PublicKeyCredential.isConditionalMediationAvailable()
]);
- Si toutes les conditions sont remplies, affichez le bouton permettant de créer une clé d'accès. Sinon, affichez un message d'avertissement.
views/home.html
if (results.every(r => r === true)) {
// If conditional UI is available, reveal the Create a passkey button.
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
Afficher les clés d'accès enregistrées dans une liste
- Définissez une fonction
renderCredentials()
qui extrait les clés d'accès enregistrées sur le serveur et les affiche dans une liste. Heureusement, vous disposez déjà du point de terminaison de serveur/auth/getKeys
permettant d'extraire les clés d'accès enregistrées pour l'utilisateur connecté.
views/home.html
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = html`${res.length > 0 ? html`
<mwc-list>
${res.map(cred => html`
<mwc-list-item>
<div class="list-item">
<div class="entity-name">
<span>${cred.name || 'Unnamed' }</span>
</div>
<div class="buttons">
<mwc-icon-button data-cred-id="${cred.id}"
data-name="${cred.name || 'Unnamed' }" @click="${rename}"
icon="edit"></mwc-icon-button>
<mwc-icon-button data-cred-id="${cred.id}" @click="${remove}"
icon="delete"></mwc-icon-button>
</div>
</div>
</mwc-list-item>`)}
</mwc-list>` : html`
<mwc-list>
<mwc-list-item>No credentials found.</mwc-list-item>
</mwc-list>`}`;
render(creds, list);
};
- Sur la ligne suivante, appelez la fonction
renderCredentials()
pour afficher les clés d'accès enregistrées dès que l'utilisateur accède à la page/home
pour l'initialisation.
views/home.html
renderCredentials();
Créer et enregistrer une clé d'accès
Pour créer et enregistrer une clé d'accès, vous devez appeler la fonction registerCredential()
que vous avez implémentée précédemment.
Pour déclencher la fonction registerCredential()
lorsque l'utilisateur clique sur le bouton Create a passkey (Créer une clé d'accès), procédez comme suit :
- Dans le fichier, après l'espace réservé HTML, recherchez l'instruction
import
suivante :
views/home.html
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
} from '/client.js';
- À la fin du corps de l'instruction
import
, ajoutez la fonctionregisterCredential()
.
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
- À la fin du fichier, après le commentaire approprié, définissez une fonction
register()
qui appelle la fonctionregisterCredential()
et une UI de chargement, puis qui appellerenderCredentials()
après un enregistrement. Cela permet de confirmer que le navigateur crée une clé d'accès et affiche un message d'erreur en cas de problème.
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
- Dans le corps de la fonction
register()
, interceptez les exceptions. La méthodenavigator.credentials.create()
génère une erreurInvalidStateError
lorsqu'une clé d'accès existe déjà sur l'appareil. Cet examen est effectué avec le tableauexcludeCredentials
. Dans ce cas, vous affichez un message pertinent à l'utilisateur. Une erreurNotAllowedError
est également générée lorsque l'utilisateur ferme la boîte de dialogue d'authentification. Dans ce cas, vous l'ignorez de façon silencieuse.
views/home.html
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates that the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
Return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
- Sur la ligne qui suit la fonction
register()
, associez la fonctionregister()
à un événementclick
pour le bouton Create a passkey (Créer une clé d'accès).
views/home.html
createPasskey.addEventListener('click', register);
Examiner le code de solution pour cette section
views/home.html
<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
<h3 class="mdc-typography mdc-typography--headline6"> Your registered
passkeys:</h3>
<div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>
views/home.html
// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
$,
_fetch,
loading,
updateCredential,
unregisterCredential,
registerCredential
} from '/client.js';
views/home.html
// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
PublicKeyCredential.isConditionalMediationAvailable) {
try {
const results = await Promise.all([
// Is platform authenticator available in this browser?
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),
// Is conditional UI available in this browser?
PublicKeyCredential.isConditionalMediationAvailable()
]);
if (results.every(r => r === true)) {
// If conditional UI is available, reveal the Create a passkey button.
createPasskey.classList.remove('hidden');
} else {
// If conditional UI isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
} catch (e) {
console.error(e);
}
} else {
// If WebAuthn isn't available, show a message.
$('#message').innerText = 'This device does not support passkeys.';
}
// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
const res = await _fetch('/auth/getKeys');
const list = $('#list');
const creds = html`${res.length > 0 ? html`
<mwc-list>
${res.map(cred => html`
<mwc-list-item>
<div class="list-item">
<div class="entity-name">
<span>${cred.name || 'Unnamed' }</span>
</div>
<div class="buttons">
<mwc-icon-button data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed' }" @click="${rename}" icon="edit"></mwc-icon-button>
<mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" icon="delete"></mwc-icon-button>
</div>
</div>
</mwc-list-item>`)}
</mwc-list>` : html`
<mwc-list>
<mwc-list-item>No credentials found.</mwc-list-item>
</mwc-list>`}`;
render(creds, list);
};
renderCredentials();
// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
try {
// Start the loading UI.
loading.start();
// Start creating a passkey.
await registerCredential();
// Stop the loading UI.
loading.stop();
// Render the updated passkey list.
renderCredentials();
} catch (e) {
// Stop the loading UI.
loading.stop();
// An InvalidStateError indicates that a passkey already exists on the device.
if (e.name === 'InvalidStateError') {
alert('A passkey already exists for this device.');
// A NotAllowedError indicates that the user canceled the operation.
} else if (e.name === 'NotAllowedError') {
Return;
// Show other errors in an alert.
} else {
alert(e.message);
console.error(e);
}
}
};
createPasskey.addEventListener('click', register);
Tester la solution
Si vous avez suivi toutes les étapes précédentes, vous avez implémenté la création, l'enregistrement et l'affichage des clés d'accès sur le site Web.
Pour tester la solution, procédez comme suit :
- Dans l'onglet d'aperçu, connectez-vous avec un nom d'utilisateur et un mot de passe aléatoires.
- Cliquez sur Create a passkey (Créer une clé d'accès).
- Validez votre identité à l'aide du verrouillage de l'écran de l'appareil.
- Vérifiez qu'une clé d'accès est enregistrée et affichée dans la section Your registered passkeys (Vos clés d'accès enregistrées) de la page Web.
Renommer et supprimer des clés d'accès enregistrées
Vous devriez être en mesure de renommer ou de supprimer les clés d'accès enregistrées de la liste. Vous pouvez vérifier comment cela fonctionne dans le code fourni dans l'atelier de programmation.
Pour Chrome, vous pouvez supprimer les clés d'accès enregistrées en accédant à la page chrome://settings/passkeys sur un ordinateur ou via le gestionnaire de mots de passe dans les paramètres sur Android.
Pour savoir comment renommer et supprimer les clés d'accès enregistrées sur d'autres plates-formes, consultez les pages d'assistance respectives de ces plates-formes.
5. Ajouter la possibilité de s'authentifier avec une clé d'accès
Les utilisateurs peuvent désormais créer et enregistrer une clé d'accès, et sont prêts à l'utiliser pour s'authentifier de manière sécurisée sur votre site Web. Vous devez maintenant ajouter une fonctionnalité d'authentification par clé d'accès à votre site Web.
Créer la fonction authenticate()
- Dans le fichier
public/client.js
, après le commentaire approprié, créez une fonction appeléeauthenticate()
qui valide l'utilisateur localement, puis sur le serveur :
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
};
Récupérer la question d'authentification et d'autres options auprès du point de terminaison du serveur
Avant de demander à l'utilisateur de s'authentifier, vous devez demander au serveur les paramètres à transmettre à WebAuthn, y compris une question d'authentification.
- Dans le corps de la fonction
authenticate()
, après le commentaire approprié, appelez la fonction_fetch()
pour envoyer une requêtePOST
au serveur :
public/client.js
// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');
Le serveur de cet atelier de programmation est conçu pour renvoyer un fichier JSON aussi semblable que possible au dictionnaire PublicKeyCredentialRequestOptions
transmis à l'API WebAuthn navigator.credentials.get()
. L'extrait de code suivant inclut des exemples des options que vous devriez recevoir :
{
"challenge": *****,
"rpId": "passkeys-codelab.glitch.me",
"allowCredentials": []
}
Le tableau suivant n'est pas exhaustif, mais contient les principaux paramètres du dictionnaire PublicKeyCredentialRequestOptions
:
Paramètres | Descriptions |
Question d'authentification générée par le serveur dans un objet | |
Un ID de RP est un domaine. Un site Web peut spécifier son propre domaine ou un suffixe enregistrable. Cette valeur doit correspondre au paramètre | |
Cette propriété permet de rechercher les authentificateurs éligibles pour cette authentification. Transmettez un tableau vide ou ne spécifiez pas ce paramètre pour permettre au navigateur d'afficher un sélecteur de compte. | |
Définissez la valeur sur |
Valider l'utilisateur localement et obtenir des identifiants
- Dans le corps de la fonction
authenticate()
, après le commentaire approprié, reconvertissez le paramètrechallenge
en binaire :
public/client.js
// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
- Transmettez un tableau vide au paramètre
allowCredentials
pour ouvrir un sélecteur de compte lorsqu'un utilisateur s'authentifie :
public/client.js
// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];
Le sélecteur de compte utilise les informations de l'utilisateur stockées avec la clé d'accès.
- Appelez la méthode
navigator.credentials.get()
avec une optionmediation: 'conditional'
:
public/client.js
// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
Cette option demande au navigateur de suggérer des clés d'accès de manière conditionnelle pour le remplissage automatique du formulaire.
Valider l'identifiant
Une fois que l'utilisateur a validé son identité localement, vous devriez recevoir un objet d'identification contenant une signature que vous pouvez vérifier sur le serveur.
L'extrait de code suivant inclut un exemple d'objet PublicKeyCredential
:
{
"id": *****,
"rawId": *****,
"type": "public-key",
"response": {
"clientDataJSON": *****,
"authenticatorData": *****,
"signature": *****,
"userHandle": *****
},
authenticatorAttachment: "platform"
}
Le tableau suivant n'est pas exhaustif, mais contient les principaux paramètres de l'objet PublicKeyCredential
:
Paramètres | Descriptions |
ID de la clé d'accès authentifiée, encodé en Base64URL. | |
Version de l'objet | |
Objet | |
Objet | |
Objet | |
Objet | |
Renvoie une chaîne |
Pour envoyer l'objet d'identification au serveur, procédez comme suit :
- Dans le corps de la fonction
authenticate()
, après le commentaire approprié, encodez les paramètres binaires de l'identifiant afin qu'ils puissent être transmis au serveur sous forme de chaîne :
public/client.js
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const authenticatorData = base64url.encode(cred.response.authenticatorData);
const signature = base64url.encode(cred.response.signature);
const userHandle = base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
- Envoyez l'objet au serveur :
public/client.js
return await _fetch(`/auth/signinResponse`, credential);
Lorsque vous exécutez le programme, le serveur renvoie HTTP code 200
, ce qui indique que l'identifiant est validé.
Vous disposez maintenant de la fonction authentication()
complète.
Examiner le code de solution pour cette section
public/client.js
// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {
// TODO: Add an ability to authenticate with a passkey: Obtain the
challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');
// TODO: Add an ability to authenticate with a passkey: Locally verify
the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
// The empty allowCredentials array invokes an account selector
by discoverable credentials.
options.allowCredentials = [];
// Invoke the WebAuthn get() function.
const cred = await navigator.credentials.get({
publicKey: options,
// Request a conditional UI.
mediation: 'conditional'
});
// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;
// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const authenticatorData =
base64url.encode(cred.response.authenticatorData);
const signature = base64url.encode(cred.response.signature);
const userHandle = base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
return await _fetch(`/auth/signinResponse`, credential);
};
6. Ajouter des clés d'accès à la saisie automatique du navigateur
Lorsque l'utilisateur revient, vous souhaitez que la procédure de connexion soit aussi facile et sécurisée que possible. Si vous ajoutez un bouton Se connecter avec une clé d'accès à la page de connexion, l'utilisateur peut appuyer sur le bouton, sélectionner une clé d'accès dans le sélecteur de compte du navigateur, puis valider son identité à l'aide du verrouillage de l'écran.
Toutefois, la transition d'un mot de passe à une clé d'accès ne se produit pas pour tous les utilisateurs en même temps. Cela implique que vous ne pouvez pas vous débarrasser des mots de passe tant que tous les utilisateurs ne sont pas passés aux clés d'accès. Vous devez donc conserver le formulaire de connexion basé sur un mot de passe en attendant. Toutefois, si vous laissez un formulaire basé sur un mot de passe et un bouton de clé d'accès, les utilisateurs devront faire un choix superflu entre les deux pour se connecter. Idéalement, vous aimeriez un processus de connexion simple.
C'est là qu'une UI conditionnelle entre en jeu. Une UI conditionnelle est une fonctionnalité WebAuthn qui vous permet de créer un champ de formulaire pour suggérer une clé d'accès comme élément de saisie automatique en plus des mots de passe. Si un utilisateur appuie sur une clé d'accès dans les suggestions de saisie automatique, il est invité à utiliser le verrouillage de l'écran de l'appareil pour valider son identité localement. Cette expérience utilisateur est fluide, car l'action de l'utilisateur est presque identique à celle d'une connexion par mot de passe.
Activer une UI conditionnelle
Pour activer une UI conditionnelle, il vous suffit d'ajouter un jeton webauthn
dans l'attribut autocomplete
d'un champ de saisie. Une fois le jeton défini, vous pouvez appeler la méthode navigator.credentials.get()
avec la chaîne mediation: 'conditional'
pour déclencher l'UI de verrouillage de l'écran de manière conditionnelle.
- Pour activer une UI conditionnelle, remplacez les champs de saisie de nom d'utilisateur existants par le code HTML suivant après le commentaire approprié dans le fichier
view/index.html
:
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
type="text"
id="username"
class="mdc-text-field__input"
aria-labelledby="username-label"
name="username"
autocomplete="username webauthn"
autofocus />
Détecter des fonctionnalités, appeler WebAuthn et activer une UI conditionnelle
- Dans le fichier
view/index.html
, après le commentaire approprié, remplacez l'instructionimport
existante par le code suivant :
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from "/client.js";
Ce code importe la fonction authenticate()
que vous avez implémentée précédemment.
- Vérifiez que l'objet
window.PulicKeyCredential
est disponible et que la méthodePublicKeyCredential.isConditionalMediationAvailable()
renvoie une valeurtrue
, puis appelez la fonctionauthenticate()
:
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
if (
window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable
) {
try {
// Is conditional UI available in this browser?
const cma =
await PublicKeyCredential.isConditionalMediationAvailable();
if (cma) {
// If conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$("#username").value = user.username;
loading.start();
location.href = "/home";
} else {
throw new Error("User not found.");
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== "NotAllowedError") {
console.error(e);
alert(e.message);
}
}
}
Examiner le code de solution pour cette section
view/index.html
<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
type="text"
id="username"
class="mdc-text-field__input"
aria-labelledby="username-label"
name="username"
autocomplete="username webauthn"
autofocus
/>
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
$,
_fetch,
loading,
authenticate
} from '/client.js';
view/index.html
// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
// Is WebAuthn avaiable in this browser?
if (window.PublicKeyCredential &&
PublicKeyCredential.isConditionalMediationAvailable) {
try {
// Is a conditional UI available in this browser?
const cma= await PublicKeyCredential.isConditionalMediationAvailable();
if (cma) {
// If a conditional UI is available, invoke the authenticate() function.
const user = await authenticate();
if (user) {
// Proceed only when authentication succeeds.
$('#username').value = user.username;
loading.start();
location.href = '/home';
} else {
throw new Error('User not found.');
}
}
} catch (e) {
loading.stop();
// A NotAllowedError indicates that the user canceled the operation.
if (e.name !== 'NotAllowedError') {
console.error(e);
alert(e.message);
}
}
}
Tester la solution
Vous avez implémenté la création, l'enregistrement, l'affichage et l'authentification de clés d'accès sur votre site Web.
Pour tester la solution, procédez comme suit :
- Accédez à l'onglet d'aperçu.
- Si nécessaire, déconnectez-vous.
- Cliquez sur la zone de texte du nom d'utilisateur. Une boîte de dialogue s'affiche.
- Sélectionnez le compte avec lequel vous souhaitez vous connecter.
- Validez votre identité à l'aide du verrouillage de l'écran de l'appareil. Vous êtes redirigé vers la page
/home
et vous êtes connecté.
7. Félicitations !
Vous avez terminé cet atelier de programmation. Si vous avez des questions, posez-les sur la liste de diffusion FIDO-DEV ou sur StackOverflow en ajoutant un tag passkey
.