← Tous les articlesIngénierie

Ajouter la connexion par clé d’accès en 2026

Les clés d’accès, c’est le déverrouillage par empreinte ou par visage que vos utilisateurs emploient déjà sur leur téléphone, appliqué à la connexion à votre application. Rien à retenir, rien à réinitialiser, et rien à dérober dans votre base de données.

Nous les avons ajoutées à Canner plus tôt cette année. Le cœur représente environ une journée de travail. Tout ce qui l’entoure a pris nettement plus longtemps, et c’est surtout de cela que parle cet article.

Ce que vous stockez

La bonne surprise, c’est le peu de choses. Pour chaque clé d’accès, vous conservez un identifiant, une clé publique, un compteur de signatures et les transports pris en charge. Rien de secret — un vidage de la table ne donne rien d’exploitable.

Deux colonnes valent la peine d’être ajoutées même si vous les ignorez au départ : backed_up et device_type, qui indiquent si une clé se synchronise vers un trousseau infonuagique. C’est précisément ce qu’on veut savoir plus tard, au moment de décider avec quelle insistance inviter quelqu’un à en inscrire une seconde.

Inscrire une clé d’accès

Le serveur émet un défi aléatoire, le navigateur le signe. Stockez le défi côté serveur et liez-le au compte qui l’a demandé — sinon un défi émis pour un compte peut être complété pour un autre.

const options = await generateRegistrationOptions({
  rpName: 'Votre application',
  rpID: 'votreapp.com',
  userID: Buffer.from(account.id, 'utf8'),
  userName: account.email,
  excludeCredentials: existing,   // pas deux fois le même appareil
  authenticatorSelection: {
    residentKey: 'preferred',     // permet la connexion sans identifiant
    userVerification: 'preferred',
  },
});

Vérifier la réponse demande trois choses : le défi stocké, votre origine et votre RP ID. Fixez les deux dernières dans la configuration, jamais depuis les en-têtes de la requête — ce sont des données fournies par l’attaquant.

rpID est la seule décision qui mérite qu’on ralentisse. C’est le domaine auquel vos clés se lient, et c’est permanent : mettez app.votreapp.com et elles fonctionnent sur ce sous-domaine seulement; mettez votreapp.com et elles fonctionnent partout en dessous. Changer ensuite ne migre rien — chaque clé inscrite devient simplement inutilisable.

Se connecter

C’est la partie à montrer. Puisque vous avez demandé un identifiant découvrable à l’inscription, la connexion n’exige aucun identifiant — envoyez un allowCredentials vide et le navigateur propose ce qu’il détient pour votre domaine.

const options = await generateAuthenticationOptions({
  rpID: 'votreapp.com',
  allowCredentials: [],   // le navigateur sait déjà
});

L’utilisateur ne tape rien. Effet secondaire : puisque vous ne demandez jamais qui se connecte, votre formulaire ne peut pas servir à tester si une adresse possède un compte. L’énumération d’utilisateurs cesse d’être un sujet.

Quatre choses qui nous ont surpris

Tout ce qui précède est bien documenté. Voici où le temps est réellement passé.

Vos sessions ne sont probablement pas révocables

Les clés d’accès améliorent le moment de la connexion et ne changent rien à ce qui suit. Si vous émettez un JWT sans état, « déconnecter partout » est un bouton que vous ne pouvez pas construire honnêtement.

Cela compte davantage sans mot de passe, puisque la réaction habituelle à une compromission — le changer — n’existe plus. La révocation de session est le remède. Nous plaçons un identifiant de session dans le jeton et consultons une pierre tombale dans Redis sur le chemin critique, la liste complète restant dans Postgres pour l’interface.

Nous avions inversé le chemin de repli

Notre première règle : si vous avez une clé d’accès mais vous connectez avec un mot de passe ou un fournisseur social, on vous laisse entrer et on envoie une alerte de sécurité. Cela semble prudent. C’est surtout une machine à apprendre aux gens à ignorer les alertes, puisqu’elle se déclenche le plus souvent après une action parfaitement raisonnable.

Une clé d’accès doit être une barrière, pas un fil de détente. Le mot de passe produit maintenant un jeton en attente de courte durée plutôt qu’une session, la clé est demandée ensuite, et seul le fait de la sauter déclenche l’alerte. Mêmes pièces, valeur par défaut inversée — et l’alerte veut désormais dire quelque chose.

La récupération doit remplacer, pas ajouter

Notre premier flux révoquait toutes les sessions et envoyait l’utilisateur inscrire une nouvelle clé. Ce qui ne sert à rien si l’attaquant a le mot de passe : il se reconnecte aussitôt.

La récupération doit révoquer les sessions, verrouiller le mot de passe, délier les comptes sociaux, retirer les anciennes clés et en inscrire une seule nouvelle. Autre chose : les analyseurs de courriel préchargent les liens. Si le lien de récupération agit sur GET, les filtres d’entreprise le consommeront avant l’utilisateur. Affichez une page; agissez sur POST.

Les petites choses

Ne demandez pas de nommer la clé — déduisez-le de l’agent utilisateur (« Chrome sur Windows ») et laissez la renommer plus tard. Détectez explicitement la prise en charge, sinon un navigateur incompatible vous donne un bouton qui ne fait rien. Permettez-en deux, c’est la façon la moins coûteuse de garder les gens hors de votre flux de récupération. Et traitez une invite biométrique fermée comme une annulation, pas comme une erreur.

Et la connexion sociale ?

Elle est plus rapide à ajouter, et pour bien des applications c’est un choix raisonnable. Le compromis, c’est une dépendance : l’accès de vos utilisateurs passe par un compte que vous ne contrôlez pas, et le fournisseur voit chaque connexion.

Le protocole n’est pas en cause — OAuth et OIDC sont des normes ouvertes avec de bons serveurs libres. Mais ce qui fait fonctionner la connexion sociale, c’est que les utilisateurs ont déjà un compte chez une poignée d’entreprises, et aucune ouverture de la spécification n’y change quoi que ce soit. Monter votre propre serveur OIDC conforme n’aide pas : personne n’y a de compte.

Les clés d’accès contournent la question en n’ayant aucun tiers. L’identifiant est généré sur l’appareil de l’utilisateur et lui appartient. Elles résistent aussi à l’hameçonnage sans dépendre de la vigilance de qui que ce soit : le navigateur refuse d’utiliser un identifiant sur une autre origine que celle où il a été créé, donc une réplique parfaite de votre page de connexion sur un domaine sosie n’obtient aucune signature.

Si vous préférez ne pas le construire

Les deux flux, c’est une journée. La révocation de session, le chemin de repli, la récupération, les courriels, l’interface de gestion et les tests, c’est quelques semaines — et rien de tout cela ne distingue votre produit de celui des autres.

C’est pourquoi nous avons empaqueté le nôtre. La raison d’avoir une table d’utilisateurs est de stocker les identifiants; si autre chose les stocke et vous remet une assertion signée, la table devient inutile. Canner Auth conserve la fiche d’utilisateur et la clé publique, et votre serveur vérifie un JWT de courte durée avec un JWKS public :

const { payload } = await jwtVerify(token, JWKS, {
  issuer: process.env.CANNER_AUTH_ISSUER,
  audience: process.env.CANNER_AUTH_APP_ID,
});

C’est toute l’intégration côté serveur. Cela convient aux applications sans serveur propre, aux projets générés par IA où l’authentification est la partie la plus faible du résultat, et à tout cas où l’authentification est un prérequis plutôt que le sujet. Déclarez les origines où votre application est servie et cela fonctionne où qu’elle soit hébergée — chez nous ou ailleurs.

Construisez-le vous-même si l’authentification est proche de votre produit ou si vous avez des exigences sur l’endroit où résident les identifiants. C’est un problème résolu avec de bonnes bibliothèques. Prévoyez simplement la seconde moitié.

Canner Auth est offert sur tous les forfaits, Démarrage compris.

À propos de l’auteur

Colin Shand est le fondateur de Canner, une plateforme de déploiement canadienne exploitée depuis le Québec. Il écrit sur l’infrastructure souveraine, l’écosystème des startups canadiennes et la création en toute indépendance.

Essayez Canner.

Déposez un projet, obtenez une URL en direct sur une infrastructure canadienne en environ 30 secondes. Forfait gratuit disponible.