Créer votre première application WebAuthn

1. Avant de commencer

L'API Web Authentication, également appelée WebAuthn, vous permet de créer et d'utiliser des identifiants de clé publique au niveau de l'origine pour authentifier les utilisateurs.

L'API est compatible avec l'utilisation d'authentificateurs U2F ou FIDO2 BLE, NFC et USB (également appelés "clés de sécurité") ainsi qu'avec l'authentificateur de plate-forme, qui permet aux utilisateurs de s'authentifier avec leur empreinte ou le verrouillage de l'écran.

Dans cet atelier de programmation, vous allez concevoir un site Web avec une fonctionnalité de réauthentification simple basée sur un lecteur d'empreinte digitale. La réauthentification protège les données de compte, car elle oblige les utilisateurs qui se sont déjà connectés à un site Web à s'authentifier à nouveau lorsqu'ils tentent d'accéder à des sections importantes de ce site ou de revenir sur le site après un certain temps.

Conditions préalables

  • Comprendre les principes de base de WebAuthn
  • Maîtriser les compétences de base en programmation avec JavaScript

Objectifs de l'atelier

  • Créer un site Web avec une fonctionnalité de réauthentification simple basée sur un lecteur d'empreinte digitale

Ce dont vous avez besoin

  • L'un des appareils suivants :
    • Un appareil Android, de préférence équipé d'un capteur biométrique
    • Un iPhone ou un iPad avec Touch ID ou Face ID sur iOS 14 ou version ultérieure
    • Un MacBook Pro ou Air avec Touch ID sur macOS Big Sur ou version ultérieure
    • Windows 10 19H1 ou version ultérieure avec Windows Hello configuré
  • L'un des navigateurs suivants :
    • Google Chrome 67 ou version ultérieure
    • Microsoft Edge 85 ou version ultérieure
    • Safari 14 ou version ultérieure

2. Configuration

Dans cet atelier de programmation, vous allez utiliser un service appelé glitch. Vous pouvez y modifier le code côté client et côté serveur avec JavaScript, et le déployer instantanément.

Accédez à https://glitch.com/edit/#!/webauthn-codelab-start.

Fonctionnement

Pour connaître l'état initial du site Web, procédez comme suit :

  1. Cliquez sur 62bb7a6aac381af8.png Show (Afficher) > 3343769d04c09851.png In a New Window (Dans une nouvelle fenêtre) pour voir le site Web en ligne.
  2. Saisissez le nom d'utilisateur de votre choix, puis cliquez sur Next (Suivant).
  3. Saisissez un mot de passe, puis cliquez sur Sign-in (Se connecter).

Le mot de passe est ignoré, mais vous êtes toujours authentifié. Vous accédez à la page d'accueil.

  1. Cliquez sur Try reauth (Essayer de vous réauthentifier), puis répétez les deuxième, troisième et quatrième étapes.
  2. Cliquez sur Sign out (Se déconnecter).

Notez que vous devez saisir le mot de passe chaque fois que vous essayez de vous connecter. Cela permet d'émuler un utilisateur qui doit s'authentifier à nouveau pour accéder à une section importante d'un site Web.

Remixer le code

  1. Accédez à l'atelier de programmation sur l'API WebAuthn/FIDO2.
  2. Cliquez sur le nom de votre projet > Remix project (Remixer le projet) 306122647ce93305.png pour le dupliquer et continuer avec votre propre version sur une nouvelle URL.

8d42bd24f0fd185c.png

3. Enregistrer un identifiant avec une empreinte

Vous devez enregistrer un identifiant généré par un UVPA, c'est-à-dire un authentificateur intégré à l'appareil qui permet de valider l'identité de l'utilisateur. Il s'agit généralement d'un lecteur d'empreinte digitale, en fonction de l'appareil de l'utilisateur.

Ajoutez cette fonctionnalité à la page /home :

260aab9f1a2587a7.png

Créer une fonction registerCredential()

Créez une fonction registerCredential() qui enregistre les nouveaux identifiants.

public/client.js

export const registerCredential = async () => {

};

Récupérer la question d'authentification et d'autres options auprès du point de terminaison du serveur

Avant de demander à l'utilisateur d'enregistrer un nouvel identifiant, demandez au serveur de renvoyer les paramètres pour qu'ils transmettent les informations d'identification dans WebAuthn, y compris une question d'authentification. Heureusement, vous avez déjà un point de terminaison de serveur qui répond avec ces paramètres.

Ajoutez le code suivant à registerCredential().

public/client.js

const opts = {
  attestation: 'none',
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    userVerification: 'required',
    requireResidentKey: false
  }
};

const options = await _fetch('/auth/registerRequest', opts);

Le protocole entre un serveur et un client ne fait pas partie de la spécification WebAuthn. Cependant, cet atelier de programmation est conçu pour respecter la spécification WebAuthn. Pour que vous puissiez l'utiliser de manière intuitive, l'objet JSON que vous transmettez au serveur ressemble beaucoup à PublicKeyCredentialCreationOptions. Le tableau suivant contient les paramètres importants que vous pouvez transmettre au serveur et explique à quoi ils servent :

Paramètres

Descriptions

attestation

Préférence pour le transfert d'attestation : none, indirect ou direct. Sélectionnez none, sauf si vous en avez besoin.

excludeCredentials

Tableau de PublicKeyCredentialDescriptor permettant à l'authentificateur d'éviter de créer des doublons.

authenticatorSelection

authenticatorAttachment

Filtre des authentificateurs disponibles. Si vous souhaitez associer un authentificateur à l'appareil, utilisez "platform". Pour les authentificateurs en itinérance, utilisez "cross-platform".

userVerification

Déterminez si la validation de l'utilisateur local par l'authentificateur est "required", "preferred" ou "discouraged". Si vous souhaitez utiliser l'authentification par empreinte ou par verrouillage de l'écran, utilisez "required".

requireResidentKey

Utilisez true si les identifiants créés doivent être disponibles pour la future expérience utilisateur de l'outil de sélection de compte.

Pour en savoir plus sur ces options, consultez la section 5.4. Options for Credential Creation (dictionary PublicKeyCredentialCreationOptions) (Options de création d'identifiants (dictionnaire PublicKeyCredentialCreationOptions)).

Voici des exemples d'options que le serveur envoie :

{
  "rp": {
    "name": "WebAuthn Codelab",
    "id": "webauthn-codelab.glitch.me"
  },
  "user": {
    "displayName": "User Name",
    "id": "...",
    "name": "test"
  },
  "challenge": "...",
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }, {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "userVerification": "required"
  }
}

Créer un identifiant

  1. Étant donné que ces options sont diffusées avec un encodage pour passer par le protocole HTTP, convertissez certains paramètres à nouveau en binaire (plus précisément, user.id, challenge et les instances de id comprises dans le tableau excludeCredentials) :

public/client.js

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);
  }
}
  1. Appelez la méthode navigator.credentials.create() pour créer un identifiant.

Grâce à cet appel, le navigateur interagit avec l'authentificateur et tente de valider l'identité de l'utilisateur à l'aide de l'UVPA.

public/client.js

const cred = await navigator.credentials.create({
  publicKey: options,
});

Une fois que l'utilisateur a validé son identité, vous devriez recevoir un objet d'identification que vous pouvez envoyer au serveur. Ensuite, enregistrez l'authentificateur.

Enregistrer les identifiants sur le point de terminaison du serveur

Voici un exemple d'objet d'identification que vous devriez avoir reçu.

{
  "id": "...",
  "rawId": "...",
  "type": "public-key",
  "response": {
    "clientDataJSON": "...",
    "attestationObject": "..."
  }
}
  1. De même que lorsque vous recevez un objet d'option permettant d'enregistrer un identifiant, vous devez encoder les paramètres binaires de l'identifiant afin de le transmettre au serveur sous forme de chaîne :

public/client.js

const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;

if (cred.response) {
  const clientDataJSON =
    base64url.encode(cred.response.clientDataJSON);
  const attestationObject =
    base64url.encode(cred.response.attestationObject);
  credential.response = {
    clientDataJSON,
    attestationObject,
  };
}
  1. Stockez l'ID d'identification localement afin de pouvoir l'utiliser pour l'authentification lorsque l'utilisateur revient :

public/client.js

localStorage.setItem(`credId`, credential.id);
  1. Envoyez l'objet au serveur et, s'il renvoie HTTP code 200, vous pouvez considérer que les nouveaux identifiants sont correctement enregistrés.

public/client.js

return await _fetch('/auth/registerResponse' , credential);

Vous disposez désormais de l'intégralité de la fonction registerCredential().

Code final de cette section

public/client.js

...
export const registerCredential = async () => {
  const opts = {
    attestation: 'none',
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      requireResidentKey: false
    }
  };

  const options = await _fetch('/auth/registerRequest', opts);

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

  const cred = await navigator.credentials.create({
    publicKey: options
  });

  const credential = {};
  credential.id =     cred.id;
  credential.rawId =  base64url.encode(cred.rawId);
  credential.type =   cred.type;

  if (cred.response) {
    const clientDataJSON =
      base64url.encode(cred.response.clientDataJSON);
    const attestationObject =
      base64url.encode(cred.response.attestationObject);
    credential.response = {
      clientDataJSON,
      attestationObject
    };
  }

  localStorage.setItem(`credId`, credential.id);

  return await _fetch('/auth/registerResponse' , credential);
};
...

4. Créer l'UI pour enregistrer, obtenir et supprimer les identifiants

Il est utile de disposer d'une liste d'identifiants enregistrés et de boutons pour les supprimer.

9b5b5ae4a7b316bd.png

Créer l'espace réservé de l'UI

Ajoutez une UI pour lister les identifiants et un bouton pour en enregistrer un nouveau. Selon que la fonctionnalité est disponible ou non, vous devez supprimer la classe hidden du message d'avertissement ou du bouton permettant d'enregistrer un nouvel identifiant. ul#list est l'espace réservé permettant d'ajouter une liste d'identifiants enregistrés.

views/home.html

<p id="uvpa_unavailable" class="hidden">
  This device does not support User Verifying Platform Authenticator. You can't register a credential.
</p>
<h3 class="mdc-typography mdc-typography--headline6">
  Your registered credentials:
</h3>
<section>
  <div id="list"></div>
</section>
<mwc-button id="register" class="hidden" icon="fingerprint" raised>Add a credential</mwc-button>

Détection des fonctionnalités et disponibilité de l'UVPA

Pour vérifier la disponibilité d'un UVPA, procédez comme suit :

  1. Examinez window.PublicKeyCredential pour vérifier si WebAuthn est disponible.
  2. Appelez PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() pour vérifier si un UVPA est disponible. S'ils sont disponibles, le bouton permettant d'enregistrer un nouvel identifiant s'affiche. Vous verrez le message d'avertissement si l'un de ces éléments n'est pas disponible.

views/home.html

const register = document.querySelector('#register');

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa) {
      register.classList.remove('hidden');
    } else {
      document
        .querySelector('#uvpa_unavailable')
        .classList.remove('hidden');
    }
  });
} else {
  document
    .querySelector('#uvpa_unavailable')
    .classList.remove('hidden');
}

Récupérer et afficher une liste d'identifiants

  1. Créez une fonction getCredentials() pour obtenir les identifiants enregistrés et les afficher dans une liste. Heureusement, vous disposez déjà d'un point de terminaison pratique sur le serveur /auth/getKeys à partir duquel vous pouvez récupérer les identifiants enregistrés pour l'utilisateur connecté.

Le fichier JSON renvoyé inclut des informations d'identification, telles que id et publicKey. Vous pouvez créer du code HTML pour le présenter à l'utilisateur.

views/home.html

const getCredentials = async () => {
  const res = await _fetch('/auth/getKeys');
  const list = document.querySelector('#list');
  const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
    <div class="mdc-card credential">
      <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
      <pre class="public-key">${cred.publicKey}</pre>
      <div class="mdc-card__actions">
        <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
      </div>
    </div>`) : html`
    <p>No credentials found.</p>
    `}`;
  render(creds, list);
};
  1. Appelez getCredentials() pour afficher les identifiants disponibles dès que l'utilisateur accède à la page /home.

views/home.html

getCredentials();

Supprimer l'identifiant

Dans la liste des identifiants, vous avez ajouté un bouton permettant de les supprimer. Pour ce faire, vous pouvez envoyer une requête à /auth/removeKey avec le paramètre de requête credId.

public/client.js

export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
  1. Ajoutez unregisterCredential à l'instruction import existante.

views/home.html

import { _fetch, unregisterCredential } from '/client.js';
  1. Ajoutez une fonction à appeler lorsque l'utilisateur clique sur Remove (Supprimer).

views/home.html

const removeCredential = async e => {
  try {
    await unregisterCredential(e.target.id);
    getCredentials();
  } catch (e) {
    alert(e);
  }
};

Enregistrer un identifiant

Vous pouvez appeler registerCredential() pour enregistrer un nouvel identifiant lorsque l'utilisateur clique sur Add a credential (Ajouter un identifiant).

  1. Ajoutez registerCredential à l'instruction import existante.

views/home.html

import { _fetch, registerCredential, unregisterCredential } from '/client.js';
  1. Appelez registerCredential() avec les options associées à navigator.credentials.create().

N'oubliez pas de renouveler la liste des identifiants en appelant getCredentials() après l'enregistrement.

views/home.html

register.addEventListener('click', e => {
  registerCredential().then(user => {
    getCredentials();
  }).catch(e => alert(e));
});

Vous devriez maintenant pouvoir enregistrer un nouvel identifiant et afficher les informations correspondantes. Vous pouvez l'essayer sur votre site Web en ligne.

Code final de cette section

views/home.html

...
      <p id="uvpa_unavailable" class="hidden">
        This device does not support User Verifying Platform Authenticator. You can't register a credential.
      </p>
      <h3 class="mdc-typography mdc-typography--headline6">
        Your registered credentials:
      </h3>
      <section>
        <div id="list"></div>
        <mwc-fab id="register" class="hidden" icon="add"></mwc-fab>
      </section>
      <mwc-button raised><a href="/reauth">Try reauth</a></mwc-button>
      <mwc-button><a href="/auth/signout">Sign out</a></mwc-button>
    </main>
    <script type="module">
      import { _fetch, registerCredential, unregisterCredential } from '/client.js';
      import { html, render } from 'https://unpkg.com/lit-html@1.0.0/lit-html.js?module';

      const register = document.querySelector('#register');

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa) {
            register.classList.remove('hidden');
          } else {
            document
              .querySelector('#uvpa_unavailable')
              .classList.remove('hidden');
          }
        });
      } else {
        document
          .querySelector('#uvpa_unavailable')
          .classList.remove('hidden');
      }

      const getCredentials = async () => {
        const res = await _fetch('/auth/getKeys');
        const list = document.querySelector('#list');
        const creds = html`${res.credentials.length > 0 ? res.credentials.map(cred => html`
          <div class="mdc-card credential">
            <span class="mdc-typography mdc-typography--body2">${cred.credId}</span>
            <pre class="public-key">${cred.publicKey}</pre>
            <div class="mdc-card__actions">
              <mwc-button id="${cred.credId}" @click="${removeCredential}" raised>Remove</mwc-button>
            </div>
          </div>`) : html`
          <p>No credentials found.</p>
          `}`;
        render(creds, list);
      };

      getCredentials();

      const removeCredential = async e => {
        try {
          await unregisterCredential(e.target.id);
          getCredentials();
        } catch (e) {
          alert(e);
        }
      };

      register.addEventListener('click', e => {
        registerCredential({
          attestation: 'none',
          authenticatorSelection: {
            authenticatorAttachment: 'platform',
            userVerification: 'required',
            requireResidentKey: false
          }
        })
        .then(user => {
          getCredentials();
        })
        .catch(e => alert(e));
      });
    </script>
...

public/client.js

...
export const unregisterCredential = async (credId) => {
  localStorage.removeItem('credId');
  return _fetch(`/auth/removeKey?credId=${encodeURIComponent(credId)}`);
};
...

5. Authentifier l'utilisateur à l'aide d'une empreinte

Vous disposez désormais d'un identifiant enregistré que vous pouvez utiliser pour authentifier l'utilisateur. Vous allez maintenant ajouter une fonctionnalité de réauthentification au site Web. Voici comment se déroule l'expérience utilisateur :

Lorsqu'un utilisateur accède à la page /reauth, un bouton Authenticate (S'authentifier) s'affiche si l'authentification biométrique est possible. L'authentification à l'aide d'une empreinte digitale (UVPA) commence lorsque l'utilisateur appuie sur Authenticate (S'authentifier), parvient à s'authentifier et accède à la page /home. Si l'authentification biométrique n'est pas disponible ou si elle échoue, l'UI bascule sur le formulaire de mot de passe existant.

b8770c4e7475b075.png

Créer une fonction authenticate()

Créez une fonction appelée authenticate(), qui valide l'identité de l'utilisateur à l'aide d'une empreinte. Vous devez ajouter du code JavaScript ici :

public/client.js

export const authenticate = async () => {

};

Récupérer la question d'authentification et d'autres options auprès du point de terminaison du serveur

  1. Avant l'authentification, vérifiez si un utilisateur dispose d'un ID d'identification stocké et, le cas échéant, définissez-le en tant que paramètre de requête.

Lorsque vous fournissez un ID d'identification ainsi que d'autres options, le serveur peut fournir des allowCredentials pertinents. Ainsi, la validation de l'utilisateur est plus fiable.

public/client.js

const opts = {};

let url = '/auth/signinRequest';
const credId = localStorage.getItem(`credId`);
if (credId) {
  url += `?credId=${encodeURIComponent(credId)}`;
}
  1. Avant d'inviter l'utilisateur à s'authentifier, demandez au serveur de renvoyer une question d'authentification et d'autres paramètres. Appelez _fetch() avec opts comme argument pour envoyer une requête POST au serveur.

public/client.js

const options = await _fetch(url, opts);

Voici des exemples d'options que vous devriez recevoir (en lien avec PublicKeyCredentialRequestOptions) :

{
  "challenge": "...",
  "timeout": 1800000,
  "rpId": "webauthn-codelab.glitch.me",
  "userVerification": "required",
  "allowCredentials": [
    {
      "id": "...",
      "type": "public-key",
      "transports": [
        "internal"
      ]
    }
  ]
}

L'option la plus importante est allowCredentials. Lorsque vous recevez des options de la part du serveur, allowCredentials doit être un objet unique dans un tableau ou un tableau vide, selon qu'un identifiant avec l'ID dans le paramètre de requête existe côté serveur.

  1. Résolvez la promesse avec null lorsque allowCredentials est un tableau vide afin que l'UI demande à nouveau un mot de passe.
if (options.allowCredentials.length === 0) {
  console.info('No registered credentials found.');
  return Promise.resolve(null);
}

Valider l'utilisateur localement et obtenir des identifiants

  1. Étant donné que ces options sont diffusées avec un encodage pour passer par le protocole HTTP, convertissez certains paramètres à nouveau en binaire (plus précisément, challenge et les instances de id comprises dans le tableau allowCredentials) :

public/client.js

options.challenge = base64url.decode(options.challenge);

for (let cred of options.allowCredentials) {
  cred.id = base64url.decode(cred.id);
}
  1. Appelez la méthode navigator.credentials.get() pour valider l'identité de l'utilisateur à l'aide d'un UVPA.

public/client.js

const cred = await navigator.credentials.get({
  publicKey: options
});

Une fois que l'utilisateur a validé son identité, vous devriez recevoir un objet d'identification que vous pouvez envoyer au serveur. Ensuite, authentifiez l'utilisateur.

Valider l'identifiant

Voici un exemple d'objet PublicKeyCredential (response correspond à AuthenticatorAssertionResponse) que vous devriez avoir reçu :

{
  "id": "...",
  "type": "public-key",
  "rawId": "...",
  "response": {
    "clientDataJSON": "...",
    "authenticatorData": "...",
    "signature": "...",
    "userHandle": ""
  }
}
  1. Encodez les paramètres binaires de l'identifiant afin qu'ils soient distribués au serveur sous forme de chaîne :

public/client.js

const credential = {};
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);

if (cred.response) {
  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,
  };
}
  1. Envoyez l'objet au serveur et, s'il renvoie HTTP code 200, vous pouvez considérer que l'utilisateur est connecté.

public/client.js

return await _fetch(`/auth/signinResponse`, credential);

Vous disposez désormais de l'intégralité de la fonction authentication().

Code final de cette section

public/client.js

...
export const authenticate = async () => {
  const opts = {};

  let url = '/auth/signinRequest';
  const credId = localStorage.getItem(`credId`);
  if (credId) {
    url += `?credId=${encodeURIComponent(credId)}`;
  }

  const options = await _fetch(url, opts);

  if (options.allowCredentials.length === 0) {
    console.info('No registered credentials found.');
    return Promise.resolve(null);
  }

  options.challenge = base64url.decode(options.challenge);

  for (let cred of options.allowCredentials) {
    cred.id = base64url.decode(cred.id);
  }

  const cred = await navigator.credentials.get({
    publicKey: options
  });

  const credential = {};
  credential.id = cred.id;
  credential.type = cred.type;
  credential.rawId = base64url.encode(cred.rawId);

  if (cred.response) {
    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. Activer l'expérience de réauthentification

Créer l'UI

Lorsque l'utilisateur revient, vous devez lui demander de s'authentifier à nouveau de la façon la plus simple et la plus sécurisée possible. C'est là qu'intervient l'authentification biométrique. Toutefois, dans certains cas, ce type d'authentification peut ne pas fonctionner :

  • L'UVPA n'est pas disponible.
  • L'utilisateur n'a pas encore enregistré d'identifiants sur son appareil.
  • L'espace de stockage est vide et l'appareil ne se souvient plus de l'ID d'identification.
  • Un problème empêche l'utilisateur de valider son identité. Par exemple, son doigt est mouillé ou il porte un masque.

C'est pourquoi il est important de toujours fournir des options de connexion de remplacement. Dans cet atelier de programmation, vous allez utiliser la solution du mot de passe basé sur un formulaire.

19da999b0145054.png

  1. Ajoutez une UI pour afficher un bouton d'authentification qui appelle l'authentification biométrique en plus du formulaire de mot de passe.

Utilisez la classe hidden pour afficher et masquer l'une d'entre elles en fonction de l'état de l'utilisateur.

views/reauth.html

<div id="uvpa_available" class="hidden">
  <h2>
    Verify your identity
  </h2>
  <div>
    <mwc-button id="reauth" raised>Authenticate</mwc-button>
  </div>
  <div>
    <mwc-button id="cancel">Sign-in with password</mwc-button>
  </div>
</div>
  1. Ajoutez class="hidden" au formulaire :

views/reauth.html

<form id="form" method="POST" action="/auth/password" class="hidden">

Détection des fonctionnalités et disponibilité de l'UVPA

Les utilisateurs doivent se connecter avec un mot de passe si l'une des conditions suivantes est remplie :

  • WebAuthn n'est pas disponible.
  • L'UVPA n'est pas disponible.
  • Aucun ID d'identification associé à cet UVPA n'est visible.

Choisissez d'afficher ou de masquer le bouton d'authentification :

views/reauth.html

if (window.PublicKeyCredential) {
  PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(uvpaa => {
    if (uvpaa && localStorage.getItem(`credId`)) {
      document
        .querySelector('#uvpa_available')
        .classList.remove('hidden');
    } else {
      form.classList.remove('hidden');
    }
  });
} else {
  form.classList.remove('hidden');
}

Basculer sur le formulaire de mot de passe

L'utilisateur doit également pouvoir choisir de se connecter avec un mot de passe.

Affichez le formulaire de mot de passe et masquez le bouton d'authentification lorsque l'utilisateur clique sur Sign in with password (Se connecter avec un mot de passe) :

views/reauth.html

const cancel = document.querySelector('#cancel');
cancel.addEventListener('click', e => {
  form.classList.remove('hidden');
  document
    .querySelector('#uvpa_available')
    .classList.add('hidden');
});

c4a82800889f078c.png

Appeler l'authentification biométrique

Pour terminer, activez l'authentification biométrique.

  1. Ajoutez authenticate à l'instruction import existante :

views/reauth.html

import { _fetch, authenticate } from '/client.js';
  1. Appelez authenticate() quand l'utilisateur appuie sur Authenticate (S'authentifier) pour lancer l'authentification biométrique.

Assurez-vous que le formulaire de mot de passe s'affiche lorsque l'authentification biométrique échoue.

views/reauth.html

const button = document.querySelector('#reauth');
button.addEventListener('click', e => {
  authenticate().then(user => {
    if (user) {
      location.href = '/home';
    } else {
      throw 'User not found.';
    }
  }).catch(e => {
    console.error(e.message || e);
    alert('Authentication failed. Use password to sign-in.');
    form.classList.remove('hidden');
    document.querySelector('#uvpa_available').classList.add('hidden');
  });
});

Code final de cette section

views/reauth.html

...
    <main class="content">
      <div id="uvpa_available" class="hidden">
        <h2>
          Verify your identity
        </h2>
        <div>
          <mwc-button id="reauth" raised>Authenticate</mwc-button>
        </div>
        <div>
          <mwc-button id="cancel">Sign-in with password</mwc-button>
        </div>
      </div>
      <form id="form" method="POST" action="/auth/password" class="hidden">
        <h2>
          Enter a password
        </h2>
        <input type="hidden" name="username" value="{{username}}" />
        <div class="mdc-text-field mdc-text-field--filled">
          <span class="mdc-text-field__ripple"></span>
          <label class="mdc-floating-label" id="password-label">password</label>
          <input type="password" class="mdc-text-field__input" aria-labelledby="password-label" name="password" />
          <span class="mdc-line-ripple"></span>
        </div>
        <input type="submit" class="mdc-button mdc-button--raised" value="Sign-In" />
        <p class="instructions">password will be ignored in this demo.</p>
      </form>
    </main>
    <script src="https://unpkg.com/material-components-web@7.0.0/dist/material-components-web.min.js"></script>
    <script type="module">
      new mdc.textField.MDCTextField(document.querySelector('.mdc-text-field'));
      import { _fetch, authenticate } from '/client.js';
      const form = document.querySelector('#form');
      form.addEventListener('submit', e => {
        e.preventDefault();
        const form = new FormData(e.target);
        const cred = {};
        form.forEach((v, k) => cred[k] = v);
        _fetch(e.target.action, cred)
        .then(user => {
          location.href = '/home';
        })
        .catch(e => alert(e));
      });

      if (window.PublicKeyCredential) {
        PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
        .then(uvpaa => {
          if (uvpaa && localStorage.getItem(`credId`)) {
            document
              .querySelector('#uvpa_available')
              .classList.remove('hidden');
          } else {
            form.classList.remove('hidden');
          }
        });
      } else {
        form.classList.remove('hidden');
      }

      const cancel = document.querySelector('#cancel');
      cancel.addEventListener('click', e => {
        form.classList.remove('hidden');
        document
          .querySelector('#uvpa_available')
          .classList.add('hidden');
      });

      const button = document.querySelector('#reauth');
      button.addEventListener('click', e => {
        authenticate().then(user => {
          if (user) {
            location.href = '/home';
          } else {
            throw 'User not found.';
          }
        }).catch(e => {
          console.error(e.message || e);
          alert('Authentication failed. Use password to sign-in.');
          form.classList.remove('hidden');
          document.querySelector('#uvpa_available').classList.add('hidden');
        });
      });
    </script>
...

7. Félicitations

Vous avez terminé cet atelier de programmation.

En savoir plus

Nous tenons à remercier Yuriy Ackermann de FIDO Alliance pour son aide.