Vérifier une adresse e-mail sans envoyer d'e-mail
Découvrez comment la vérification d’e-mail par navigateur prouve le contrôle d’une boîte sans envoyer de message : formulaire, vérificateur Node.js en cinq étapes et limites de l’essai.
L’Email Verification Protocol permet à un site de confirmer qu’un utilisateur contrôle une adresse e-mail sans lui envoyer de message : le navigateur obtient un jeton signé auprès du fournisseur de messagerie de l’utilisateur et le transmet avec le formulaire.
La plupart des parcours d’inscription affichent encore « Consultez votre boîte de réception », et une partie des personnes qui changent d’onglet pour récupérer le code ne reviennent jamais. Chrome teste une alternative. L’origin trial Chrome annoncé le 8 juillet 2026 a débuté avec Chrome 150, et Chrome a ensuite publié une mise à jour en août 2026. Le protocole lui-même est défini dans l’Internet-Draft de l’IETF draft-hardt-email-verification.
Il ne s’agit pas d’une vérification SMTP ni d’un nettoyage de liste par enregistrement MX. Ces outils vérifient qu’une boîte aux lettres existe. Ce protocole prouve que la personne qui remplit le formulaire est connectée à cette boîte. Les sections suivantes présentent le modèle à trois parties, le balisage du formulaire, un vérificateur Node.js complet découpé en cinq étapes, et le degré de confiance que l’on peut lui accorder pendant la phase de test.
Points clés
- L’Email Verification Protocol renvoie un SD-JWT avec liaison de clé (key binding) : un Email Verification Token signé par l’émetteur, suivi d’un tilde, puis d’un JWT de liaison de clé signé par le navigateur.
- Le navigateur s’interpose entre votre site et le fournisseur de messagerie : le fournisseur émet donc le jeton sans savoir quel site l’a demandé.
- Un jeton valide prouve l’existence d’une session active auprès du fournisseur de messagerie pour cette adresse. Il ne prouve pas que les e-mails envoyés à cette adresse seront délivrés.
- La fonctionnalité est une proposition testée dans un origin trial Chrome, avec des limites de trafic et sans date de fin dans les publications de Chrome. Dans leurs diapositives de l’IETF 126, les auteurs du draft (et non Chrome) estimaient sa durée entre trois et six mois. La documentation de Chrome ne cite que Gmail comme fournisseur participant.
- Si le champ caché du jeton arrive vide, ou si l’une des étapes de vérification échoue, envoyez votre e-mail de confirmation habituel.
Que remplace la vérification d’e-mail par le navigateur ?
La vérification d’e-mail par le navigateur remplace l’e-mail contenant un code à usage unique ou un lien magique, qui fait quitter votre site à l’utilisateur. Le draft de l’IETF détaille ce que le parcours par code exige de l’utilisateur : ouvrir son application de messagerie, attendre la réception, éventuellement fouiller dans les spams, copier le code, puis revenir et le coller. Le draft ajoute que de nombreuses personnes abandonnent en cours de route. Un lien magique évite le copier-coller, mais l’utilisateur doit tout de même se rendre dans sa boîte de réception et attendre le message.
La connexion via un réseau social (social login) se passe entièrement d’e-mail. Le draft en souligne le coût : l’utilisateur doit posséder un compte chez ce fournisseur, et vous devez développer une intégration par fournisseur. L’Email Verification Protocol repose sur le même principe (le fournisseur se porte garant de l’adresse), mais passe par le navigateur, sans SDK propre à un fournisseur sur votre site.
Fonctionnement de l’Email Verification Protocol : trois parties
L’Email Verification Protocol fait intervenir trois parties. Votre site est le vérificateur (la partie de confiance, ou relying party). Le navigateur se situe entre vous et l’émetteur, c’est-à-dire le service qui gère le compte de messagerie de l’utilisateur. Comme tous les échanges transitent par le navigateur, l’émetteur ne sait jamais quel site a demandé le jeton.
Verifier (your site) <--> Browser <--> Issuer (e.g. accounts.google.com)
nonce, validation binds token checks session,
to your origin signs EVT
Le fournisseur de messagerie et l’émetteur peuvent être des hôtes distincts. Dans l’exemple de Chrome, le fournisseur est gmail.com et l’émetteur est accounts.google.com.
Côté émetteur, le domaine de messagerie publie un enregistrement DNS TXT qui délègue à l’émetteur. L’émetteur expose les métadonnées /.well-known/email-verification, signale l’état de connexion au navigateur via la Login Status API et, avant de signer quoi que ce soit, vérifie les cookies de session first-party existants de l’utilisateur (consultez Secure Cookies and HttpOnly pour savoir comment configurer ces cookies). Le draft permet aussi au navigateur de demander des adresses privées propres à chaque site, que l’EVT signale par is_private_email: true. La documentation de l’origin trial de Chrome ne décrit pas les adresses privées. Les révisions antérieures du draft permettaient à l’émetteur de se rabattre sur un challenge WebAuthn en l’absence de cookie valide, mais la révision -02 a supprimé cette option : une requête sans session échoue donc tout simplement.
Côté site : balisage du formulaire et nonce
De votre côté, le formulaire a besoin de deux champs : un champ e-mail classique avec autocomplete="email", et un champ caché avec autocomplete="email-verification-token" qui porte un nonce lié à la session de l’utilisateur. À la soumission du formulaire, le navigateur remplit le champ caché avec le jeton. Voici le balisage tiré de la publication de Chrome, avec le nonce généré par le serveur :
<input
name="email-address"
type="email"
autocomplete="email">
<input
type="hidden"
name="token"
nonce="rAnD0m-VaLuE"
autocomplete="email-verification-token">
Les règles de liaison à la session du draft exigent un nonce aléatoire d’au moins 128 bits d’entropie. Générez-en un nouveau pour chaque demande de vérification et faites-le expirer rapidement. Stockez l’heure de rendu à côté, car l’étape 2 du vérificateur en a besoin :
import { randomBytes } from "node:crypto";
const nonce = randomBytes(32).toString("base64url"); // 256 bits
req.session.evp = { nonce, renderedAt: Date.now() };
La page doit également contenir votre jeton d’origin trial. La documentation des origin trials de Chrome explique comment le fournir. D’après la mise à jour d’août, les origin trials tiers ne sont pas pris en charge pour cette fonctionnalité.
Vérifier le jeton côté serveur
La vérification côté serveur d’un jeton Email Verification Protocol se déroule en cinq étapes : l’analyser, contrôler les valeurs attendues, contrôler la liaison de clé, contrôler la délégation DNS et vérifier la signature de l’émetteur. La valeur du formulaire est <EVT>~<KB-JWT>. L’EVT est le jeton signé par l’émetteur et contient email, email_verified, iss, iat ainsi qu’une clé de navigateur cnf.jwk. Le KB-JWT est signé par le navigateur et contient aud, nonce, iat et sd_hash. Ne vous fiez à aucune claim décodée tant que toutes les étapes ci-dessous n’ont pas réussi. Le code utilise jose et les modules natifs de Node. Chaque bloc s’exécute dans un bloc try au sein d’une seule fonction verifyEmailToken(), qui renvoie false si une exception est levée.
Voici les imports nécessaires aux cinq étapes :
import { createHash } from "node:crypto";
import { resolveTxt } from "node:dns/promises";
import {
createRemoteJWKSet,
decodeJwt,
decodeProtectedHeader,
importJWK,
jwtVerify,
} from "jose";
1. Analyse. Séparez la valeur au niveau du tilde et contrôlez les en-têtes typ.
const [evt, kb, ...rest] = token.split("~");
if (!evt || !kb || rest.length) return false;
const evtHeader = decodeProtectedHeader(evt);
const kbHeader = decodeProtectedHeader(kb);
if (evtHeader.typ !== "evt+jwt" || kbHeader.typ !== "kb+jwt") return false;
const evtClaims = decodeJwt(evt);
const kbClaims = decodeJwt(kb);
Les jetons actuels ne comportent aucune divulgation sélective (selective disclosure), la valeur se découpe donc proprement en deux parties. La mise à jour d’août qualifie ce raccourci de fragile, car il cesserait de fonctionner si des divulgations étaient ajoutées un jour. Chrome recommande d’analyser le jeton avec une bibliothèque SD-JWT comme @sd-jwt/core, que sa propre démo utilise désormais.
2. Valeurs attendues. Les étapes de validation de Chrome contrôlent email_verified, email, nonce, aud, ainsi qu’un iat postérieur au rendu du formulaire. Comparez l’adresse e-mail sans tenir compte de la casse : la mise à jour d’août prévient que les fournisseurs peuvent renvoyer l’adresse avec une casse différente de celle saisie par l’utilisateur.
const { nonce, renderedAt } = req.session.evp ?? {};
delete req.session.evp; // single use
if (evtClaims.email_verified !== true) return false;
if (String(evtClaims.email).toLowerCase() !== submittedEmail.toLowerCase()) return false;
if (!nonce || kbClaims.nonce !== nonce) return false;
if (kbClaims.aud !== "https://example.com") return false;
if (kbClaims.iat < Math.floor(renderedAt / 1000)) return false;
// Any maximum age is your own policy; the sources define none.
3. Liaison de clé. sd_hash est le SHA-256 de l’EVT, ~ final compris. Le KB-JWT doit également être vérifié à l’aide de la clé contenue dans cnf.jwk.
const hash = createHash("sha256").update(evt + "~").digest("base64url");
if (hash !== kbClaims.sd_hash) return false;
const holderKey = await importJWK(evtClaims.cnf.jwk, kbHeader.alg);
await jwtVerify(kb, holderKey, { typ: "kb+jwt" });
4. Délégation DNS. Interrogez _email-verification.<domain>. Le draft exige exactement un enregistrement TXT, dont la valeur doit commencer par iss=. Utilisez dig +short TXT _email-verification.gmail.com pour vérifier un domaine manuellement.
const domain = submittedEmail.split("@").pop().toLowerCase();
const records = (await resolveTxt(`_email-verification.${domain}`))
.map((chunks) => chunks.join(""));
if (records.length !== 1 || !records[0].startsWith("iss=")) return false;
const issuer = records[0].slice(4);
5. Découverte et signature de l’EVT. Récupérez les métadonnées de l’émetteur, puis vérifiez l’EVT à l’aide de son jwks_uri. Le draft définit l’identifiant de l’émetteur comme https:// suivi de l’hôte issu de l’enregistrement DNS, et la claim iss de l’EVT doit lui correspondre exactement.
const meta = await (await fetch(`https://${issuer}/.well-known/email-verification`)).json();
const jwks = createRemoteJWKSet(new URL(meta.jwks_uri));
const { payload } = await jwtVerify(evt, jwks, {
algorithms: meta.signing_alg_values_supported,
typ: "evt+jwt",
}); // written so it does not depend on a kid header
// The draft's issuer identifier is "https://" plus the DNS host, compared byte for byte.
if (payload.iss !== `https://${issuer}`) return false;
return true;
| Contrôle | Élément | En cas d’échec |
|---|---|---|
| Analyse | Découpage sur ~, en-têtes typ | Envoyer l’e-mail de confirmation |
| Valeurs attendues | email_verified, email, nonce, aud, iat | Envoyer l’e-mail de confirmation |
| Liaison de clé | sd_hash, signature du KB-JWT vs cnf.jwk | Envoyer l’e-mail de confirmation |
| DNS | enregistrement TXT iss= unique | Envoyer l’e-mail de confirmation |
| Signature de l’émetteur | métadonnées, jwks_uri, iss | Envoyer l’e-mail de confirmation |
Le module DNS de Node renvoie chaque enregistrement TXT sous forme de tableau de fragments, d’où la concaténation à l’étape 4.
Quelles sont les limites de la vérification d’e-mail par le navigateur ?
Une vérification Email Verification Protocol réussie montre que l’utilisateur est actuellement connecté à son fournisseur de messagerie avec cette adresse. Elle ne dit rien sur la bonne réception des e-mails que vous y enverrez. Chrome et le draft insistent tous deux sur ce point : continuez donc d’envoyer vos e-mails de bienvenue.
Le profil de navigateur dans lequel l’utilisateur remplit le formulaire doit déjà disposer d’une session active auprès du fournisseur, et la première fois qu’une adresse est vérifiée, l’utilisateur voit s’afficher une demande d’autorisation, une seule fois par adresse. Dans la version de juillet, l’adresse devait provenir de la saisie automatique. D’après la mise à jour d’août, saisir ou coller l’adresse déclenche désormais la vérification lorsque le champ perd le focus. Le draft signale aussi une légère fuite d’information : la présence ou non d’un jeton indique à votre site si l’utilisateur est connecté auprès de l’émetteur.
Que faire dès maintenant ?
L’Email Verification Protocol n’est pas encore prêt à remplacer votre e-mail de confirmation. L’API navigateur est en incubation au WICG, et le protocole est un Internet-Draft individuel (révision -02, août 2026). Chrome plafonne le trafic pendant le test afin que les sites ne s’appuient pas sur la fonctionnalité avant son lancement. Les publications de Chrome n’indiquent aucune date de fin. L’estimation de trois à six mois provient des auteurs du draft, dans leurs diapositives DISPATCH de l’IETF 126, et non de Chrome. Chrome prévient également de possibles changements non rétrocompatibles, et la mise à jour d’août précise que la fonctionnalité est réservée au desktop jusqu’à Chrome 152, la prise en charge d’Android étant encore à l’étude. La documentation de Chrome ne cite que Gmail comme fournisseur participant. Le draft mentionne aussi Hellō comme émetteur implémentant le protocole, ainsi qu’un origin trial sur Microsoft Edge.
Le mécanisme d’émission a déjà changé une fois. La publication de juillet décrivait un JWT request_token encodé en tant que formulaire. Le draft le remplace par les HTTP Message Signatures (RFC 9421) et marque l’ancien format comme obsolète. À partir de Chrome 153, Chrome envoie du application/json avec les en-têtes Signature, Signature-Input et Signature-Key, et un corps contenant uniquement l’adresse e-mail. Ce changement concerne uniquement l’émetteur et n’affecte pas le code de votre vérificateur.
Conservez votre parcours par OTP ou lien magique comme chemin par défaut. Si vous participez au test, traitez le jeton comme une amélioration progressive : lorsqu’il est vérifié, sautez l’étape de l’e-mail. Après la vérification, créez votre session comme vous le faites habituellement (voir Cookies vs localStorage for JWT Auth). L’EVT+KB est une preuve à usage unique et ne doit pas servir de jeton de session.
Le protocole remplace « allez consulter votre boîte de réception » par une vérification de signature exécutée sur votre propre serveur, et le vérificateur ne nécessite que cinq étapes. Ajoutez le champ caché et le nonce, déployez verifyEmailToken() en amont de votre e-mail de confirmation existant, et consultez la publication de mise à jour de Chrome avant chaque nouvelle version du navigateur.
FAQ
En quoi l'Email Verification Protocol diffère-t-il de Sign in with Google ?
Sign in with Google est une intégration de connexion propre à un fournisseur, qui crée une relation d'identité entre votre site et Google. L'Email Verification Protocol se contente de confirmer que l'utilisateur contrôle une adresse e-mail. Il ne nécessite aucun SDK de fournisseur sur votre site et, comme le navigateur sert d'intermédiaire, l'émetteur signe le jeton sans savoir quel site l'a demandé.
Puis-je utiliser crypto.randomUUID() pour générer le nonce ?
Non. Un UUID version 4 ne contient que 122 bits aléatoires, car les champs de version et de variante occupent les 6 bits restants. Le draft de l'IETF exige un nonce d'au moins 128 bits d'entropie. Utilisez plutôt randomBytes(32).toString('base64url') de node:crypto, qui fournit 256 bits. Stockez le nonce dans la session serveur et supprimez-le après une seule utilisation.
L'Email Verification Protocol fonctionne-t-il pour les adresses sur des domaines personnalisés ou chez d'autres fournisseurs que Gmail ?
Uniquement si le domaine de messagerie publie exactement un enregistrement TXT _email-verification, commençant par iss=, qui délègue à un émetteur prenant en charge le protocole. La documentation de Chrome cite Gmail comme fournisseur de messagerie participant. Pour tout autre domaine dépourvu de cet enregistrement, le navigateur ne peut pas obtenir de jeton : votre vérificateur doit donc considérer la vérification comme échouée et envoyer l'e-mail de confirmation habituel.