Tout mot de passe accepté par votre application peut être deviné, réutilisé sur un autre site, saisi sur une fausse page de connexion, ou fuiter depuis une sauvegarde de base de données. Les passkeys suppriment ces quatre problèmes d'un coup. L'utilisateur se connecte avec l'empreinte, le visage ou le code PIN qui déverrouille déjà le téléphone ou l'ordinateur portable, et il n'y a plus, sur votre serveur, de secret qui vaille la peine d'être volé.
Dans l'aperçu des nouveaux composants de connexion, les passkeys n'avaient droit qu'à cinq points, et l'article sur SAML couvrait la connexion pour les entreprises qui gèrent leur propre fournisseur d'identité. Cet article couvre le volet sans mot de passe : comment TsgcWSAPIServer_WebAuthn enregistre les passkeys, connecte les utilisateurs sans nom d'utilisateur, affiche les passkeys dans la liste d'autofill du navigateur et conserve chaque credential dans votre propre base de données.
Ce qu'est une passkey
Une passkey est une paire de clés créée par l'authenticator de l'utilisateur : Windows Hello, le trousseau iCloud, le gestionnaire de mots de passe Google, un gestionnaire de mots de passe ou une clé de sécurité FIDO2. La clé privée ne quitte jamais l'authenticator. Votre serveur ne stocke que la clé publique, si bien qu'une table de passkeys qui fuite ne permet à personne de se connecter. Chaque signature est liée au domaine de votre site, si bien qu'un domaine de phishing qui y ressemble n'obtient rien d'exploitable. C'est ce que signifie ici résistant au phishing.
En termes WebAuthn, une passkey est un credential détectable, aussi appelé resident key. L'authenticator conserve le userHandle à côté de la clé privée, ce qui explique pourquoi l'utilisateur peut se connecter sans taper de nom d'utilisateur et pourquoi le navigateur peut lister seul les passkeys de votre site.
Demander des credentials détectables
WebAuthn exige un contexte sécurisé : servez vos pages en https, ou depuis localhost pendant le développement, et réglez WebAuthnOptions.RelyingParty sur le nom d'hôte affiché par le navigateur. Le residentKey demandé par les options d'enregistrement provient de WebAuthnOptions.DefaultOptions.Registration.DiscoverableCredential :
waundcPreferred, la valeur par défaut. L'authenticator crée un credential détectable quand il le peut.waundcRequired. Seuls les credentials détectables sont acceptés. Utilisez-le pour les passkeys et la connexion sans nom d'utilisateur.waundcDiscouraged. L'authenticator devrait créer un credential côté serveur, qui nécessite le nom d'utilisateur pour se connecter.
Une requête d'enregistrement isolée peut toujours écraser la valeur par défaut avec le champ discoverable_credential, réglé sur required, preferred ou discouraged.
Se connecter sans nom d'utilisateur
Demandez les options d'authentification avec un nom d'utilisateur vide. Les options ne portent alors aucune liste allowCredentials, le navigateur affiche les passkeys qu'il détient pour votre relying party, et l'utilisateur en choisit une. Il n'y a rien à taper, donc rien à mal taper.
Comme le serveur n'a pas choisi le credential cette fois, il vérifie davantage de choses. La réponse doit inclure le userHandle, le userHandle doit appartenir au credential qui a signé, et un credential que votre application ne connaît pas est rejeté.
Les passkeys dans la liste d'autofill
L'autofill, ou conditional mediation, place les passkeys de votre site dans la liste de suggestions du champ nom d'utilisateur, à côté des mots de passe enregistrés. C'est la manière en douceur de faire migrer les utilisateurs : la page de connexion continue de fonctionner pour ceux qui ont un mot de passe, et ceux qui ont une passkey la choisissent dans la liste.
- Ajoutez
autocomplete="username webauthn"au champ nom d'utilisateur. - Chargez
/sgcWebAuthn.jsdans la page. Le composant le sert lui-même, depuis l'endpoint défini dansEndpointsOptions.WebAuthn. - Au chargement de la page, demandez les options sans nom d'utilisateur et appelez
startAuthentication(options, true). La promise se résout lorsque l'utilisateur choisit une passkey dans la liste.
L'autofill nécessite un navigateur avec conditional mediation, ce qui aujourd'hui signifie les versions actuelles de Chrome, Edge et Safari. browserSupportsWebAuthnAutofill() renvoie False dans les autres, gardez donc aussi un bouton « Se connecter avec une passkey » sur la page.
Plusieurs passkeys, un seul userHandle
Les vrais utilisateurs ont plus d'une passkey : une sur l'ordinateur portable, une sur le téléphone, peut-être une clé de sécurité pour le jour où les deux sont perdues. Enregistrez chacune avec le même nom d'utilisateur. Quand ce nom d'utilisateur possède déjà des credentials connus du serveur, le nouvel enregistrement réutilise leur userHandle (user.id), si bien que chaque passkey du compte partage un seul userHandle et mène toutes au même utilisateur.
Le serveur connaît les credentials enregistrés pendant son exécution et ceux que vous ajoutez avec AddCredential. Quand vos passkeys vivent dans une base de données, ajoutez-les au démarrage pour que chaque compte conserve un seul userHandle. Quand un utilisateur tape bien un nom d'utilisateur, les options d'authentification listent chaque passkey de cet utilisateur dans allowCredentials, et l'authenticator utilise celle qu'il détient.
Conservez les passkeys dans votre propre base de données
Le composant ne persiste pas les credentials. Vos passkeys se rangent à côté de votre table d'utilisateurs, et quatre événements relient les deux :
OnWebAuthnRegistrationSuccessful. Enregistrez le nouveau credential record, par exempleaCredentialRecord.AsJSON, avec sonCredentialIdet sonUsername.OnWebAuthnAuthenticationOptionsRequest. Avec un nom d'utilisateur, ajoutez les passkeys de cet utilisateur àCredentialRecords. Sans nom d'utilisateur, n'ajoutez rien.OnWebAuthnAuthenticationGetCredential. Se déclenche quand le credential choisi par le navigateur n'est pas dans la liste de la ceremony, ce qui est le cas de toute connexion sans nom d'utilisateur et de tout autofill. Recherchez le credential, remplissezaCredentialRecordet définissezFound.OnWebAuthnAuthenticationSuccessful. Enregistrez le nouveauSignCountetBackupStatedeaAuthentication.Credential.CredentialRecord, puis créez la session.
Enregistrer une passkey et la recharger tiennent en deux handlers courts :
uses
sgcWebAuthn_Classes;
// registration: store the whole record, keyed by its credential id
procedure TForm1.WebAuthnWebAuthnRegistrationSuccessful(Sender: TObject;
const aRegistration: TsgcWebAuthn_Registration;
const aCredentialRecord: TsgcWebAuthn_CredentialRecord; var Accept: Boolean);
begin
DBInsertPasskey(aCredentialRecord.CredentialId, aCredentialRecord.Username,
aCredentialRecord.AsJSON);
end;
// usernameless and autofill sign-in: the browser chose the passkey,
// find it by its credential id and hand it back to the server
procedure TForm1.WebAuthnWebAuthnAuthenticationGetCredential(Sender: TObject;
const aCredentialId: string;
const aCredentialRecord: TsgcWebAuthn_CredentialRecord; var Found: Boolean);
var
vJSON: string;
begin
Found := DBFindPasskey(aCredentialId, vJSON);
if Found then
aCredentialRecord.ReadJSON(vJSON);
end;
DBInsertPasskey et DBFindPasskey représentent votre propre code d'accès aux données. L'enregistrement que vous retournez doit porter le CredentialId demandé, et le même Username quand la ceremony a débuté avec un nom d'utilisateur, sinon la connexion échoue. Conservez aussi le UserId dans l'enregistrement stocké, car le serveur le compare au userHandle envoyé par l'authenticator. Les événements s'exécutent dans les threads de connexion du serveur, protégez donc les ressources partagées comme vous les protégez partout ailleurs dans le serveur.
Synchronisées ou liées à l'appareil
Les indicateurs présents dans les données de l'authenticator indiquent quel type de passkey vous avez reçu, et le credential record les conserve sous BackupEligible (l'indicateur BE) et BackupState (l'indicateur BS) :
- BackupEligible True, BackupState True. Une passkey synchronisée, sauvegardée par la plateforme ou le gestionnaire de mots de passe et disponible sur les autres appareils de l'utilisateur.
- BackupEligible True, BackupState False. Une passkey multi-appareils qui n'est pas encore sauvegardée.
- BackupEligible False, BackupState False. Une passkey liée à l'appareil, telle qu'une clé de sécurité ou une clé TPM qui ne quitte jamais l'appareil. Suggérez à l'utilisateur d'enregistrer une seconde passkey, car perdre cet appareil signifie perdre le credential.
Votre politique peut les traiter différemment, par exemple en n'acceptant que les clés de sécurité liées à l'appareil sur les comptes administrateur. À chaque connexion, le serveur rejette une réponse dont l'indicateur BS est défini et l'indicateur BE ne l'est pas, ainsi qu'une réponse dont l'indicateur BE diffère du BackupEligible stocké, car l'eligibility d'un credential ne change jamais. Le nouvel indicateur BS est copié dans BackupState, et c'est dans OnWebAuthnAuthenticationSuccessful qu'il faut l'enregistrer.
Le compteur de signatures et les authenticators clonés
Certains authenticators incrémentent un compteur de signatures à chaque connexion. Quand le compteur de la réponse ou le SignCount stocké n'est pas zéro, le compteur de la réponse doit être supérieur à la valeur stockée. Sinon, la connexion est rejetée, car deux authenticators répondant avec la même clé, c'est exactement à quoi ressemble un authenticator cloné.
Cette vérification ne fonctionne que si vous enregistrez le compteur après chaque connexion, et que la valeur stockée n'avance jamais que vers l'avant. Les passkeys synchronisées signalent généralement 0 à chaque fois, ce qui désactive la vérification pour elles. Si vous avez des authenticators qui n'incrémentent pas le compteur de façon fiable, réglez WebAuthnOptions.AllowSignCountLessOrEqualStoredValue sur True pour les accepter. La valeur stockée n'est quand même jamais abaissée.
Essayez la démo
La démo Demos\26.Authentication\01.Passkeys est un relying party complet : un TsgcWebSocketHTTPServer avec un TsgcWSAPIServer_WebAuthn attaché, servant une petite page de connexion sur https://localhost:5443. Elle stocke chaque passkey dans son propre fichier passkeys.json, via les quatre événements ci-dessus.
- Compilez la démo et gardez libcrypto-3.dll et libssl-3.dll à côté de l'exécutable. Ils se trouvent dans le dossier de la démo.
- Laissez l'hôte 127.0.0.1, le port 5443 et le relying party localhost, puis cliquez sur Start.
- Cliquez sur Open Browser et acceptez le certificat de test autosigné.
- Tapez un nom d'utilisateur et cliquez sur Register passkey. Enregistrez une seconde passkey pour le même nom d'utilisateur. Le formulaire liste chaque passkey avec son type, synchronisée ou liée à l'appareil, et son compteur de signatures.
- Cliquez sur Sign in without user name et choisissez une passkey. Le serveur la retrouve via
OnWebAuthnAuthenticationGetCredentialet le journal affiche l'utilisateur. - Rechargez la page et cliquez dans le champ nom d'utilisateur. Les passkeys apparaissent dans la liste d'autofill. Choisissez-en une pour vous connecter.
- Redémarrez l'application. Les passkeys se rechargent depuis passkeys.json, ce qui montre que le stockage appartient à votre application et non au composant.
Documentation
Où l'obtenir
TsgcWSAPIServer_WebAuthn est inclus dans les éditions Enterprise et All-Access de sgcWebSockets, pour Delphi et C++ Builder, et le même composant fait partie de sgcWebSockets .NET. Si vous n'avez besoin que de l'authentification, le pack sgcAuth l'inclut avec les autres composants de connexion. Vous le trouvez sur la palette SGC Auth, et rien ne change dans une application existante tant que vous ne le déposez pas sur un formulaire.
À lire aussi
- Connexion Delphi avec Passkeys, SSO SAML, LDAP et TOTP 2FA
- Authentification unique SAML en Delphi avec Entra ID, Okta et AD FS
- WebAuthn, passkeys et la fin des mots de passe
Voir la vidéo
Il existe une courte vidéo, « Passkeys in Delphi: passwordless login with WebAuthn », sur la chaîne eSeGeCe. Elle montre le code dans l'IDE, ce que révèlent les indicateurs de sauvegarde et le compteur de signatures, ainsi que la démo tournant sur https://localhost : un utilisateur enregistre deux passkeys, puis se connecte sans taper de nom d'utilisateur.
Des questions, un retour, ou besoin d'aide pour ajouter des passkeys à votre page de connexion ? Contactez-nous. Vous recevrez une réponse des personnes qui ont écrit le code.
