Passkeys pour le développement web : WebAuthn, RP ID, vérification serveur et solutions de repli

Les mots de passe restent un moyen familier de se connecter à un site, mais leurs problèmes sont connus : secrets réutilisés, hameçonnage, tentatives de connexion avec des identifiants volés et demandes d’assistance après un oubli. Les passkeys, ou clés d’accès, proposent un autre modèle. Le navigateur s’appuie sur un authentificateur pour prouver que la personne dispose d’un identifiant cryptographique pour le site. Le serveur vérifie une signature au lieu de recevoir un mot de passe réutilisable.

Le principe paraît simple, mais plusieurs détails comptent en production : le domaine autorisé à utiliser une passkey, le rattachement de chaque défi à une tentative de connexion, la récupération de l’accès depuis un nouvel appareil et les tests des parcours qui traversent plusieurs sites. Ce guide explique ces mécanismes sans présenter quelques lignes de JavaScript comme un système d’authentification complet.

Une passkey n’est pas un mot de passe enregistré dans le navigateur

Une passkey repose sur la cryptographie à clé publique. L’authentificateur crée une paire de clés : la clé privée reste dans l’authentificateur ou dans son environnement protégé de gestion des identifiants, tandis que le site conserve une clé publique associée au compte. Lors de la connexion, l’authentificateur signe un défi fourni par le serveur ainsi que des données de contexte. Le serveur vérifie le résultat.

Ce mécanisme évite de remettre un mot de passe réutilisable à un site d’hameçonnage. Le navigateur et l’authentificateur imposent le périmètre du site demandeur : le domaine trompeur examp1e.com ne peut pas simplement demander les identifiants de example.com. Certains fournisseurs synchronisent les passkeys ; d’autres les conservent sur une clé de sécurité ou un appareil précis. Les possibilités de récupération et de synchronisation ne sont pas identiques partout.

Une passkey prouve le contrôle d’un identifiant, pas la sécurité automatique de chaque session ou action sur le compte. L’application a toujours besoin de gérer les sessions, de contrôler les autorisations, de prévenir les attaques CSRF lorsque c’est pertinent, de limiter les tentatives et de sécuriser la récupération de compte. Le guide de localStorage, sessionStorage et des cookies explique pourquoi les sessions gérées par le serveur sont distinctes de la création d’un identifiant.

Les deux opérations : inscription et authentification

WebAuthn définit deux parcours principaux. L’inscription crée un nouvel identifiant. L’authentification utilise un identifiant existant pour signer un défi. Les deux demandent la coopération du navigateur et d’un service côté serveur.

Voici une séquence d’inscription simplifiée :

  1. La personne lance l’inscription depuis les paramètres d’un compte authentifié, ou depuis un parcours de création de compte soigneusement conçu.
  2. Le serveur crée un défi neuf et imprévisible ainsi que des options d’inscription pour ce compte, avec une date d’expiration.
  3. Le navigateur appelle navigator.credentials.create() et l’authentificateur produit un identifiant.
  4. Le navigateur transmet la réponse au serveur.
  5. Le serveur la valide par rapport à la demande enregistrée, contrôle l’origine et le RP ID attendus, applique sa politique et conserve la clé publique et les métadonnées de l’identifiant.

L’authentification suit un principe comparable, mais le navigateur appelle navigator.credentials.get(). Le serveur vérifie l’assertion signée, le défi, l’origine, l’empreinte du RP ID et les indicateurs de présence et de vérification de l’utilisateur exigés par sa politique. Il crée ensuite une session applicative.

Ces parcours se prêtent bien à un diagramme de séquence. L’éditeur Mermaid permet de représenter la personne, le navigateur, l’authentificateur et le serveur sans enfouir les responsabilités de sécurité dans une seule fonction de contrôleur. Le tutoriel sur les diagrammes d’API montre comment représenter les réussites et les échecs.

Le RP ID et l’origine ne sont pas interchangeables

La sécurité de WebAuthn repose sur l’identité du site. L’origine comprend le schéma, l’hôte et le port, par exemple https://login.example.com. Le relying party ID, ou RP ID, est un identifiant de domaine sans schéma ni port.

Par défaut, le domaine de la page sert de RP ID. Un sous-domaine peut choisir un domaine parent si celui-ci constitue un suffixe de domaine enregistrable de son hôte ; un suffixe public comme com n’est pas autorisé. Ainsi, une inscription sur register.example.com peut utiliser example.com afin qu’une page sur login.example.com puisse aussi utiliser cet identifiant. Un domaine sans lien ne peut pas revendiquer example.com simplement parce qu’une application partage ses comptes entre deux marques.

Pour des déploiements sur des domaines enregistrables différents, le mécanisme Related Origin Requests peut autoriser certaines relations s’il est pris en charge et correctement configuré. Il utilise un document JSON publié à l’adresse /.well-known/webauthn sur le domaine du RP ID et demande de vérifier la compatibilité des navigateurs. Ce n’est ni un cookie partagé ni un en-tête CORS. Consultez la documentation de passkeys.dev sur les origines associées (en anglais) pour connaître ses conditions de déploiement et de prise en charge.

Côté navigateur : demander les options d’inscription au serveur

Les navigateurs modernes exposent WebAuthn par l’API de gestion des identifiants. Cet exemple suppose que le serveur fournit le format de sérialisation JSON de WebAuthn et que les méthodes de conversion récentes sont disponibles. Il s’agit d’une ébauche côté client, pas d’un système de connexion complet :

async function beginPasskeyRegistration() {
  if (!window.isSecureContext || !window.PublicKeyCredential ||
      !navigator.credentials ||
      typeof PublicKeyCredential.parseCreationOptionsFromJSON !== "function" ||
      typeof PublicKeyCredential.prototype.toJSON !== "function") {
    throw new Error("Passkey registration is unavailable here");
  }

  const optionsResponse = await fetch("/api/passkeys/register/options", {
    credentials: "same-origin",
    cache: "no-store"
  });
  if (!optionsResponse.ok) throw new Error("Cannot start registration");

  const optionsJSON = await optionsResponse.json();
  const publicKey = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJSON);
  const credential = await navigator.credentials.create({ publicKey });
  if (!credential) throw new Error("No passkey was created");

  const verifyResponse = await fetch("/api/passkeys/register/verify", {
    method: "POST",
    credentials: "same-origin",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(credential.toJSON())
  });
  if (!verifyResponse.ok) throw new Error("Registration was not accepted");
}

En production, le serveur doit associer cette demande d’inscription à un compte déjà authentifié et autorisé, prévoir une protection CSRF lorsque c’est nécessaire, limiter la durée de validité du défi et valider toute la réponse WebAuthn avec une bibliothèque maintenue. Vérifiez la disponibilité des méthodes récentes, notamment parseCreationOptionsFromJSON() et toJSON(), dans les navigateurs pris en charge. Pour les clients plus anciens, utilisez un adaptateur d’encodage éprouvé plutôt que des conversions improvisées de chaînes en données binaires.

Le bouton qui lance le parcours doit afficher clairement l’attente, gérer l’annulation et annoncer la réussite uniquement après acceptation par le serveur. La fermeture de la boîte de dialogue système n’est pas un incident de sécurité et ne doit pas déclencher un message alarmant.

Ce que le serveur doit vérifier

Pour une connexion sans mot de passe, demandez des identifiants découvrables et exigez la vérification de l’utilisateur selon votre politique. Faites respecter cette exigence côté serveur, en plus de la demander au navigateur. Toucher une clé de sécurité, ce qui prouve une présence, est différent de la déverrouiller par un code PIN ou une donnée biométrique, ce qui vérifie l’utilisateur.

Une requête reçue sur /verify ne prouve pas qu’une inscription ou une connexion a réussi. Le serveur doit valider les résultats cryptographiques et les confronter aux attentes de la demande initiale. Les contrôles comprennent notamment :

  • Le défi est récent, appartient à l’opération en attente et n’a pas déjà été consommé.
  • L’origine retournée correspond à une origine que l’application autorise pour cette demande.
  • L’empreinte du RP ID correspond au site demandeur prévu.
  • L’opération WebAuthn et le type d’identifiant sont ceux attendus.
  • Pour l’authentification, la signature est valide avec la clé publique enregistrée, et les indicateurs de présence ou de vérification de l’utilisateur respectent la politique.
  • À l’inscription, l’algorithme retourné et les données d’attestation sont validés selon la politique avant l’enregistrement de l’identifiant et de sa clé publique.
  • Les identifiants sont rattachés au bon compte, avec une gestion des doublons, des compteurs et de l’état de sauvegarde conforme à la bibliothèque et au comportement de l’authentificateur. Un compteur qui reste à zéro ne prouve pas, à lui seul, un échec ou un clonage.

Ces points de contrôle ne constituent pas un algorithme de vérification complet : appliquez les procédures d’inscription et d’authentification de la spécification WebAuthn, normative et en anglais, par l’intermédiaire d’une bibliothèque serveur maintenue. Écrire soi-même l’analyseur CBOR, le traitement des données de l’authentificateur et la vérification des signatures est un mauvais raccourci pour une application courante. Le guide MDN des passkeys (en anglais) aide à comprendre le protocole avant de choisir une bibliothèque éprouvée pour votre langage.

Pour déboguer les échanges HTTP, le générateur de commandes cURL aide à reproduire les appels JSON ordinaires. Il ne peut pas produire une véritable assertion signée par un authentificateur : les opérations cryptographiques WebAuthn exigent un authentificateur et le bon contexte navigateur. La réussite d’un appel d’API simulé ne prouve pas qu’une connexion par passkey a été vérifiée.

Connexion et remplissage automatique

À la connexion, le serveur crée des options d’authentification avec un nouveau défi et une politique adaptée aux identifiants. Le navigateur appelle navigator.credentials.get() et retourne une assertion. Certains sites proposent un bouton “Se connecter avec une passkey” ; d’autres intègrent les passkeys au champ du compte grâce à l’interface conditionnelle, lorsqu’elle est disponible.

Dans les deux cas, conservez un autre parcours visible et utilisable pour les personnes dont l’appareil, le navigateur ou le gestionnaire d’identifiants ne peut pas satisfaire la demande. N’interprétez pas automatiquement une annulation comme l’absence de passkey sur le compte. La personne utilise peut-être un autre appareil ou préfère simplement ne pas se connecter à cet instant.

La récupération et la migration sont des choix de produit

Avant d’activer un bouton “Supprimer le mot de passe”, répondez aux questions pratiques. Peut-on enregistrer plusieurs authentificateurs ? Comment récupérer un compte après la perte de toutes les passkeys ? Que se passe-t-il si un salarié perd l’accès à un appareil d’entreprise ? Qui peut ajouter ou retirer un identifiant, et quelles actions exigent une authentification récente ?

La récupération de compte fait partie du modèle de sécurité. Une récupération reposant uniquement sur l’e-mail, des contrôles d’assistance trop faibles ou des codes de secours réutilisables peuvent affaiblir une connexion pourtant résistante à l’hameçonnage. Permettez l’ajout de passkeys lorsque c’est possible, protégez les modifications des moyens de récupération et avertissez les utilisateurs lors de l’ajout ou du retrait d’un identifiant. Si des mots de passe restent nécessaires pendant la migration, privilégiez des secrets uniques et difficiles à deviner. Le générateur de mots de passe et de phrases de passe convient aux comptes de test jetables et aux identifiants de repli qui seront conservés en lieu sûr.

Tester les échecs aussi soigneusement que les réussites

Un plan de test sur plusieurs navigateurs doit au minimum couvrir :

  1. L’inscription et la connexion avec un authentificateur intégré à la plateforme et, lorsque c’est possible, une clé de sécurité externe.
  2. Un défi expiré ou réutilisé, une origine inattendue et un identifiant inconnu. Le serveur doit rejeter chacun de ces cas de manière sûre.
  3. L’annulation de la demande d’authentification sans perdre l’état du formulaire.
  4. La connexion depuis un second appareil, y compris l’authentification entre appareils si la combinaison de navigateurs et d’appareils visée la prend en charge.
  5. Le comportement entre sous-domaines ou origines associées, si votre déploiement en dépend.
  6. La récupération de compte et le retrait d’un identifiant, avec prévention du rejeu et invalidation des sessions.

Utilisez le guide d’inspection des requêtes réseau pour observer les points d’accès qui fournissent les options et vérifient les réponses. Soyez prudent lors de l’export des journaux : même si les clés privées n’atteignent jamais le serveur, les captures peuvent révéler des identifiants de compte, des défis, des cookies de session et d’autres informations sensibles. Le panneau Réseau n’est qu’une partie des tests : les dialogues du navigateur, l’état de l’authentificateur et la validation serveur comptent aussi.

L'offre Premium arrive bientôt

Un abonnement Premium facultatif permettant de supprimer les publicités est prévu. Les abonnements ne sont pas encore disponibles.

Vous pouvez continuer à utiliser nos outils gratuits sans compte.