Connexion sans mot de passe : passkeys, SMS, OAuth ou magic link, lequel choisir
Ou : Comment choisir un serrurier quand toutes les serrures se valent à peu près
Le magic link, la passkey, le code reçu par SMS et le bouton « Se connecter avec Google » promettent la même chose : une connexion sans mot de passe. Aucun ne supprime la confiance. Chacun la déménage, vers une boîte mail, vers une puce dans un téléphone, vers le guichet d’un opérateur ou vers les serveurs d’un géant du web. Comparer ces quatre méthodes d’authentification, c’est donc comparer des serruriers plutôt que des serrures, et se demander lequel tu veux devoir appeler le jour où la porte reste fermée. Une seule propriété les sépare nettement, la résistance à l’hameçonnage. Une seule des quatre la possède, et ce n’est pas celle que cette série a passé quatre articles à construire.
Le premier article promettait ce comparatif « une fois le coût réel d’une implémentation propre sous les yeux ». Le voici sous les yeux : un token dont la base ne garde que l’empreinte, une demande qui répond pareil à tout le monde, une vérification à usage unique liée au navigateur. Cet article pose les quatre méthodes dans la même grille, branche une passkey et une connexion OpenID Connect sur le système de la série, en Go et tests compris, puis fait l’inventaire : la pyramide de tests, et douze points à vérifier avant la mise en production.
Magic link, passkeys, OTP par SMS ou OAuth : l’authentification sans mot de passe comparée
Cinq questions, quatre réponses
Une grille de comparaison vaut ce que valent ses questions. J’en garde cinq, celles qu’on se pose en production plutôt qu’en démonstration.
| Magic link | Passkey | Code par SMS | OAuth / OpenID Connect | |
|---|---|---|---|---|
| Résiste à l’hameçonnage | Non | Oui | Non | Autant que la connexion chez le fournisseur |
| À qui tu délègues | Au fournisseur de messagerie | À l’appareil et à son gestionnaire de clés | À l’opérateur téléphonique | À Google, Microsoft ou un autre fournisseur d’identité |
| Coût par connexion | Un e-mail | Rien | Un SMS, 0,11 dollar vers un mobile belge chez Twilio | Rien |
| Depuis un autre appareil | Non, le lien est lié au navigateur qui l’a demandé | Oui, par QR code et téléphone à proximité | Oui | Oui |
| Ce qui casse en premier | Le mail en retard, filtré, ou la boîte perdue | L’appareil perdu sans copie synchronisée | La carte SIM détournée | Une panne ou un compte suspendu chez le fournisseur |
La première ligne est la seule qui répond par oui ou par non, et c’est la plus importante. Le NIST l’a tranchée dans sa révision d’août 2025 : un facteur qui demande de recopier une valeur ne peut pas être considéré comme résistant à l’hameçonnage, parce que la recopie ne lie pas le code à la session en cours1. Six chiffres tapés sur une fausse page sont aussitôt rejoués sur la vraie.
Le magic link ne se recopie pas, mais il se relaie. Le quatrième article le reconnaissait : celui qui demande le lien depuis son propre navigateur détient le cookie de liaison, et il ne lui manque que le lien. Une fausse page qui demande à la victime de « coller le lien reçu pour confirmer » suffit. C’est plus rare que six chiffres, parce que le geste est moins naturel. Ce n’est pas impossible.
Le magic link, vu de l’extérieur
Vu de l’extérieur, le magic link a quatre défauts, et aucun ne se corrige dans le code.
Le premier : le compte vaut ce que vaut la boîte mail, puisque qui la lit peut demander le lien lui-même. Le deuxième : la latence. Le courrier électronique repose sur un protocole de 1982, conçu pour que le message arrive un jour, pas dans les trente secondes. Un serveur de messagerie qui pratique la liste grise (greylisting) refuse temporairement le premier envoi d’un expéditeur inconnu, et la RFC qui décrit la technique note que certains serveurs ne réessaient pas avant une heure ou plus2. Une heure, pour un formulaire de connexion, c’est une éternité suivie d’un ticket au support.
Le troisième défaut vient de mes propres choix. Le cookie de l’article précédent lie le lien au navigateur qui l’a demandé. C’est ce qui bloque le lien transféré, et c’est aussi ce qui empêche de demander le lien sur l’ordinateur et de l’ouvrir sur le téléphone. Le contournement habituel consiste à glisser un code à six chiffres dans le même mail. Il rouvre exactement ce que la recopie ouvre : la saisie sur une fausse page. Chaque confort ajouté au magic link rend un peu de ce que l’article précédent avait fermé.
Le quatrième : la délivrabilité. Depuis le 1er février 2024, Gmail exige de tout expéditeur une authentification SPF ou DKIM de son domaine3. Un lien envoyé sans elle part, et n’arrive pas. Le magic link est le seul des quatre dont le bon fonctionnement dépend d’un filtre antispam que personne ne contrôle.
La passkey, seule méthode qui lit l’adresse du site
Une passkey, ou clé d’accès, est une paire de clés. La clé privée reste dans l’appareil ou dans son gestionnaire de clés ; le serveur ne garde que la clé publique, qu’une fuite de base rend inutile. À la connexion, le serveur envoie un défi, l’appareil le signe, le serveur vérifie la signature. Rien de neuf jusque-là. La différence est ailleurs : ce n’est pas l’utilisateur qui présente la clé, c’est le navigateur. Et le navigateur sait sur quel site il se trouve. Il inscrit l’origine de la page dans les données signées, et l’appareil ne propose que les clés enregistrées pour ce domaine. Une fausse page n’a rien à demander, parce que personne ne recopie quoi que ce soit.
Côté Go, une bibliothèque fait le travail cryptographique : go-webauthn, en version 0.18.2, qui décode les réponses de l’appareil et vérifie attestations et signatures. La configuration tient en trois lignes, et deux d’entre elles portent toute la résistance à l’hameçonnage.
| |
Reste à écrire quatre handlers, deux pour enregistrer une passkey, deux pour se connecter, sur le modèle de ceux de l’article précédent. L’enregistrement révèle le rôle que le magic link garde dans ce système : on n’ajoute une passkey qu’à un compte déjà ouvert, et ce compte s’est ouvert par le lien.
| |
ResidentKeyRequirementRequired demande une clé découvrable, rangée dans l’appareil avec l’identifiant du compte. C’est elle qui permet la connexion sans rien taper : le serveur ne demande plus d’adresse, le navigateur propose les passkeys du site, et le serveur apprend à qui il parle en lisant la réponse. L’anti-énumération du troisième article devient sans objet, puisqu’il n’y a plus rien à énumérer.
| |
p.start est le StartSession de l’article précédent, inchangé. La passkey et le magic link aboutissent à la même session opaque, révocable d’un DELETE. Le défi, lui, suit la règle du token : rangé côté serveur derrière un cookie __Host-, lu une fois, oublié aussitôt.
Deux tentatives d’hameçonnage, deux serrures
Je voulais voir la résistance à l’hameçonnage échouer pour de vrai, pas la lire dans une spécification. Le test tourne dans Chromium 154, piloté par Playwright, avec l’authentificateur virtuel du protocole DevTools : une passkey logicielle que le navigateur traite comme une vraie. Trois serveurs tournent en local. Le vrai site écoute sur localhost:18080 ; un faux site se fait passer pour lui sur une autre adresse, 127.0.0.1:18082 ; un troisième occupe une origine voisine, localhost:18081, comme le ferait un sous-domaine compromis. Le scénario : connexion par magic link, enregistrement d’une passkey, déconnexion, puis l’attaquant récupère un défi authentique sur le vrai site et le présente depuis ses pages.
| Tentative | Où elle s’arrête | Réponse |
|---|---|---|
Le vrai site, localhost:18080 | nulle part | 204, session ouverte, sans adresse saisie |
Faux domaine, 127.0.0.1:18082 | dans le navigateur | SecurityError: This is an invalid domain. |
Origine voisine, localhost:18081 | sur le serveur | signature obtenue, puis Error validating origin |
La deuxième ligne est la promesse des passkeys : le faux domaine n’atteint même pas l’appareil. La troisième est la plus instructive. L’origine voisine appartient au même domaine que l’identifiant de la passkey, alors le navigateur accepte de signer. Mais il a écrit http://localhost:18081 dans les données signées, et le serveur, qui n’accepte que les origines de RPOrigins, refuse. Deux serrures, donc : le navigateur vérifie le domaine, le serveur vérifie l’origine exacte. La seconde ne tient que si la liste reste exacte. Un joker ajouté par commodité dans RPOrigins, et ton sous-domaine de préproduction oublié devient un guichet de signatures.
La passkey n’est pas pour autant la fin de l’histoire. Elle a deux faiblesses, et la seconde concerne directement cette série. La première tient à l’appareil. Une passkey synchronisée par le trousseau d’Apple, le gestionnaire de Google ou un gestionnaire tiers survit à la perte d’un téléphone ; une clé matérielle unique, non. Le NIST admet les clés synchronisées au niveau AAL2 et les exclut du niveau AAL3, parce que leur clé privée sort de l’appareil1. Le passage d’un gestionnaire à l’autre progresse : Apple a ouvert l’export et l’import des passkeys avec iOS 26, Android a suivi en juin 2026 par une mise à jour des services Google Play4. Quant à l’adoption, elle n’est plus un pari : la FIDO Alliance estimait en mai 2026 à 5 milliards le nombre de passkeys en circulation, et Microsoft ouvre depuis le 1er mai 2025 ses nouveaux comptes sans mot de passe par défaut5.
La seconde faiblesse est que la passkey ne supprime pas la récupération. Elle la repousse. Le jour où l’utilisateur perd tous ses appareils, il faut bien lui rendre son compte par un autre chemin. Et la sécurité d’un compte se mesure à son chemin de récupération le plus faible, pas au plus solide.
Le SMS, ou la confiance confiée à un guichet
Le code par SMS délègue la confiance à l’opérateur téléphonique, et plus précisément à l’employé qui, au guichet ou au téléphone, accepte de transférer un numéro vers une nouvelle carte SIM. L’échange de carte SIM n’a rien d’exotique : le FBI a enregistré en 2024 aux États-Unis 982 plaintes pour 25,98 millions de dollars de pertes déclarées6, un chiffre qui ne compte que les victimes qui ont identifié la cause et pris la peine de la signaler.
Les autorités ont tranché. Le NIST classe le réseau téléphonique parmi les authentificateurs « restreints », tolérés à condition d’en mesurer le risque, et l’ANSSI recommande de ne pas utiliser le SMS pour recevoir un facteur d’authentification. Le classement complet des seconds facteurs, de la clé FIDO2 au SMS, figure dans la double authentification classée face à l’hameçonnage, avec les fiches de la CISA et de l’ANSSI.
Il y a aussi la facture. Au 2 octobre 2026, Twilio affiche 0,1113 dollar par SMS envoyé vers un mobile belge7. Mille connexions par jour coûtent donc environ 110 dollars par jour, et ce n’est pas le pire. Le pire s’appelle le gonflement artificiel du trafic, ou SMS pumping. Un robot saisit dans ton formulaire des milliers de numéros appartenant à des plages choisies, ton fournisseur envoie un SMS à chacun, et l’opérateur complice reverse aux fraudeurs une part des revenus. Ton formulaire de connexion devient un distributeur automatique dont tu fournis les billets.
Le SMS garde un usage : un public qui n’a ni smartphone récent ni adresse mail consultée, et pour qui le numéro de téléphone est le seul canal. C’est un public réel. Il mérite alors une limite de débit sévère et une liste des pays autorisés à recevoir des codes.
OAuth et OpenID Connect : déléguer la porte, et ses pannes avec
Le bouton « Se connecter avec Google » a des arguments sérieux. Pas de mail à délivrer, pas de SMS à payer, et la connexion se fait chez un fournisseur qui protège déjà ses comptes par passkey ou double authentification. L’utilisateur n’a rien de nouveau à retenir. Toi non plus.
En échange, ta porte d’entrée devient celle de quelqu’un d’autre. Le 14 décembre 2020, pendant 47 minutes, les services de Google qui exigeaient une connexion OAuth sont devenus indisponibles : un système automatique de quotas avait réduit les ressources du service qui gère les identifiants des comptes8. Pendant ces 47 minutes, un site qui ne proposait que Google n’avait pas de bogue. Il avait le bogue de quelqu’un d’autre. Un compte suspendu chez le fournisseur se ferme aussi chez toi, par ricochet. Et ce fournisseur, comme un VPN sans logs, habite quelque part sous des lois, ce qui n’est pas un détail quand il sait qui se connecte à quoi, et quand.
Le protocole, lui, ne t’oblige pas à choisir un géant américain. En Belgique, itsme repose sur le même flux de code d’autorisation d’OpenID Connect, et le code qui suit fonctionne avec lui en changeant l’émetteur9. Le portefeuille européen d’identité numérique, que chaque État membre doit proposer d’ici fin 2026, ouvrira d’autres options.
Le piège d’OpenID Connect n’est pas cryptographique, la bibliothèque go-oidc vérifie la signature, l’émetteur, l’audience et l’échéance du jeton. Le piège est la liaison des comptes. La tentation consiste à retrouver le compte local par l’adresse mail que le jeton contient. C’est exactement la faille baptisée nOAuth en juin 2023 : chez Microsoft, l’adresse mail d’un compte pouvait être modifiée par son propriétaire sans vérification, et une application qui identifiait ses utilisateurs par cette adresse ouvrait le compte de la victime à quiconque inscrivait son adresse dans un annuaire à lui10.
| |
La spécification le dit noir sur blanc : la paire émetteur et sujet est la seule identité stable qu’un jeton garantit. Et la liaison d’une identité externe à un compte existant se fait depuis une session déjà ouverte, donc, ici encore, après un magic link. Le test le vérifie avec un faux fournisseur d’identité monté sur httptest, qui signe ses jetons en RS256 avec une clé générée pour l’occasion.
| |
Le jeton de Mallory est authentique, correctement signé, et porte l’adresse d’Alice. Il ne l’ouvre pas, parce que son sujet n’est lié à rien. Un second test présente un jeton signé par une autre clé, et la vérification le rejette avant toute recherche de compte.
Le jour où Dave supprime le magic link
DevOps Dave : J’ai lu le comparatif. On passe tout en passkeys et on supprime le magic link. Moins de code, plus de sécurité.
Security Sarah : Et quand Alice perd son téléphone ?
DevOps Dave : On lui envoie un code par SMS pour qu’elle enregistre une nouvelle passkey.
Security Sarah : Donc le compte protégé par la méthode qui résiste à l’hameçonnage s’ouvre avec la méthode qui y résiste le moins.
DevOps Dave : … On garde le magic link pour la récupération ?
Security Sarah : Et tu demandes à tes utilisateurs de protéger leur boîte mail par une passkey. Ta serrure la plus solide ne vaut rien si la porte de service reste en carton.
Dave a raisonné méthode par méthode, comme on remplit une grille. Mais un compte n’a pas une méthode d’authentification. Il en a plusieurs, l’officielle et celles de secours, et l’attaquant choisit toujours la plus faible.
La combinaison que je garde
Pour un site comme celui-ci, la réponse tient en une phrase : la passkey en méthode principale, proposée après la première connexion, et le magic link pour l’inscription et la récupération. Le NIST impose d’ailleurs aux services fédéraux américains de niveau AAL2 de proposer au moins une méthode résistante à l’hameçonnage1, ce qui reste une bonne règle hors des États-Unis. Les autres contextes appellent d’autres réponses.
| Contexte | Méthode principale | Récupération |
|---|---|---|
| Site grand public, comme celui-ci | Passkey, proposée après le premier magic link | Magic link |
| Outil interne d’une PME sous Microsoft 365 ou Google Workspace | OpenID Connect vers l’annuaire de l’entreprise | L’administrateur de l’annuaire |
| Service belge qui doit connaître l’identité réelle | itsme en OpenID Connect | Une procédure hors ligne |
| Public sans smartphone ni mail consulté | Code par SMS, faute de mieux | Un support humain |
Le magic link n’est donc plus la porte d’entrée. Il devient la porte de service, celle qu’on emprunte une fois pour entrer et une fois par an pour retrouver ses clés. C’est précisément pour ça qu’il fallait la blinder.
La pyramide de tests de la série
Cinq articles, sept étages de tests. Chacun a attrapé quelque chose que les autres laissaient passer, ce qui est la seule justification honnête d’une stratégie de test à plusieurs étages.
| Étage | Ce qu’il attrape | Dans la série |
|---|---|---|
| Test unitaire avec de faux composants | la logique, l’expiration sans attendre quinze minutes | TestTokenExpires, horloge et TokenStore en mémoire (article 2) |
| Fuzzing | les formats que personne n’a imaginés | FuzzHashToken (article 2) |
httptest | le contrat HTTP, les réponses identiques | TestSameAnswerForKnownAndUnknown (article 3), TestAnotherBrowserCannotUseTheLink (article 4) |
| Intégration PostgreSQL | les courses dans la base | TestConsumeHasOneWinner, cent rondes (article 4) |
Détecteur -race | les courses en mémoire | toute la suite, aveugle aux courses SQL |
| Faux fournisseur d’identité | la signature, la liaison des comptes | TestSameEmailOtherSubjectIsNotTheVictim (article 5) |
| Navigateur réel | l’origine, la cérémonie WebAuthn | Chromium et authentificateur virtuel (article 5) |
Deux lignes résument la leçon. Le détecteur de courses est resté vert sur une consommation de token qui ouvrait vingt sessions, et seul un test contre une vraie base, dans un conteneur jetable avec Testcontainers, l’a vu. La vérification d’origine des passkeys, elle, n’existe que dans un navigateur : aucun test unitaire ne fabrique une origine voisine aussi bien que Chromium.
Tout le code de cet article a été compilé et testé sous Go 1.27.1, go vet et le détecteur de courses compris, avec go-webauthn 0.18.2, go-oidc 3.21.0 et golang.org/x/oauth2 0.37.0. Le parcours passkey a tourné de bout en bout dans Chromium 154.
Douze points avant la mise en production
| # | Vérification | Article |
|---|---|---|
| 1 | Token de 32 octets tirés de crypto/rand, dont la base ne garde que l’empreinte SHA-256 | 2 |
| 2 | Durée de vie courte, quinze minutes, et consommation en un seul DELETE ... RETURNING | 2 et 4 |
| 3 | Réponse identique pour une adresse connue et une adresse inconnue | 3 |
| 4 | Limite de débit par adresse et par IP | 3 |
| 5 | Envoi du mail hors de la requête HTTP, pour que le temps de réponse ne trahisse rien | 3 |
| 6 | SPF et DKIM sur le domaine d’envoi, DMARC en plus | 5 |
| 7 | Le GET affiche une page, seul le POST consomme le token | 4 |
| 8 | Cookie __Host- qui lie le lien au navigateur demandeur | 4 |
| 9 | Referrer-Policy: no-referrer et Cache-Control: no-store sur les pages qui voient le token | 4 |
| 10 | Session neuve à chaque connexion, opaque et révocable | 4 |
| 11 | Une méthode résistante à l’hameçonnage proposée, avec des origines exactes dans RPOrigins | 5 |
| 12 | Comptes externes liés par émetteur et sujet, jamais par l’adresse mail | 5 |
Le premier article disait que le mot de passe est une dette que l’utilisateur rembourse à ta place, et que le magic link la transfère à sa boîte mail. Cinq articles plus tard, la conclusion tient en une ligne de plus : aucune des quatre méthodes ne rembourse la dette, elles choisissent le créancier. La messagerie, l’opérateur, Google, ou l’utilisateur lui-même, enfin, son téléphone. La passkey est la seule où le créancier se trouve dans la poche de l’intéressé, et c’est pour ça qu’elle résiste à l’hameçonnage. C’est aussi pour ça que, le jour où le téléphone tombe dans un lac, tout le monde se retourne vers la vieille porte de service. Tu viens de passer quatre articles à la blinder. Ce n’était pas du temps perdu.
La révision 4 du NIST SP 800-63B, publiée en août 2025, est sans ambiguïté sur la recopie : les authentificateurs « that involve the manual entry of an authenticator output (e.g., out-of-band and OTP authenticators) SHALL NOT be considered phishing-resistant ». Elle range le réseau téléphonique parmi les authentificateurs restreints, interdit les clés synchronisées au niveau AAL3 et impose aux vérificateurs du niveau AAL2 d’offrir « at least one phishing-resistant authentication option ». ↩︎ ↩︎ ↩︎
La RFC 6647, publiée en 2012, décrit le greylisting comme un service volontairement dégradé pour les expéditeurs inconnus, sous forme d’erreur temporaire. Elle prévient que « some popular MTAs do not retry failed delivery attempts for an hour or more, which can cause expensive delays when delivery of mail is time critical ». Un lien de connexion est l’exemple même du courrier pressé. ↩︎
Les exigences de Google envers les expéditeurs, en vigueur depuis le 1er février 2024, imposent SPF ou DKIM à tous, et SPF, DKIM et DMARC à ceux qui envoient plus de 5 000 messages par jour à des adresses Gmail. DMARC coûte un enregistrement DNS : l’économiser n’a jamais été une bonne affaire. ↩︎
Chez Apple, ASCredentialExportManager, introduit avec iOS 26 et macOS 26, applique le protocole d’échange de la FIDO Alliance, d’application à application et sans fichier intermédiaire. Chez Google, la version 26.21 des services Google Play, du 1er juin 2026, permet d’importer et d’exporter « passwords and passkeys » avec le même standard. ↩︎
La FIDO Alliance a publié ce chiffre le 7 mai 2026, en précisant qu’il s’agit d’une estimation fondée sur des données publiques et sur ses propres données de déploiement. Microsoft a annoncé le 1er mai 2025 que les nouveaux comptes seraient « passwordless by default », avec un taux de réussite de 98 % pour les connexions par passkey contre 32 % pour les mots de passe. Chiffres d’éditeur, mais l’écart survit à une bonne marge d’optimisme. ↩︎
Le rapport 2024 de l’Internet Crime Complaint Center du FBI (PDF, 47 pages) recense 982 plaintes et 25 983 946 dollars de pertes pour l’échange de carte SIM. Il y en avait 2 026, pour 72,7 millions, en 2022. La baisse tient peut-être à la façon de déclarer : l’échange de SIM ouvre souvent un autre vol, et c’est ce vol-là qui figure dans la plainte. ↩︎
La grille tarifaire de Twilio pour la Belgique indique 0,1113 dollar par message sortant vers un mobile, relevé le 2 octobre 2026, frais d’opérateur éventuels en sus. La documentation de Twilio sur le SMS pumping décrit le mécanisme : les fraudeurs exploitent un champ de numéro de téléphone pour déclencher des envois vers des numéros d’opérateurs choisis, et « the fraudsters get a share of the generated revenue ». ↩︎
Le rapport d’incident de Google Cloud l’écrit en une phrase : « for a duration of 47 minutes, customer-facing Google services that required Google OAuth access were unavailable ». La cause : une migration de quotas à moitié finie, dont l’ancien système déclarait une consommation nulle pour le service d’identifiants. Il est rassurant de savoir que Google aussi a des migrations à moitié finies. ↩︎
La documentation d’itsme l’annonce d’emblée : « itsme API is based on the Authorization Code Flow of OpenID Connect 1.0 ». Pour le portefeuille européen, la Commission européenne indique que chaque État membre proposera au moins une version du portefeuille « by 2026 », en application du règlement (UE) 2024/1183. ↩︎
Le billet du Microsoft Security Response Center du 20 juin 2023 conclut : « Applications should never use the email claim for authorization due to its mutability and non-uniqueness ». La faille a été signalée par la société Descope. La section 5.7 de la spécification OpenID Connect Core le disait depuis le début : « the only guaranteed unique identifier for a given End-User is the combination of the iss Claim and the sub Claim ». ↩︎