Pour implémenter la validation des adresses e-mail sur votre site, mettez à jour le balisage de votre formulaire afin de demander le jeton et ajoutez la validation côté serveur pour les jetons entrants.
S'inscrire à la version d'évaluation
Les sites à valider doivent avoir configuré l'Origin Trial.
- S'inscrire à la phase d'évaluation de l'origine
- Diffuser le jeton de la phase d'évaluation de l'origine
À partir de Chrome 154, les versions d'essai d'origine tierce sont compatibles avec une mise en garde importante : l'origine enregistrée pour la version d'essai doit être sur le même site que l'émetteur. Exemple :
- Domaine de l'émetteur :
issuer.example - Demandeur d'enregistrement OT :
https://issuer.example - Origine JavaScript :
https://issuer.example(ouhttps://app.issuer.exampleavec la correspondance de sous-domaine)
Configurer les champs du formulaire
Ajoutez un champ de jeton masqué à votre formulaire d'envoi d'e-mails :
<input
type="email"
name="email-address"
autocomplete="email">
<input
type="hidden"
name="token"
autocomplete="email-verification-token"
nonce="rAnD0m-VaLuE">
Exigences concernant les champs :
- Champ "Adresse e-mail" : définissez
type="email"etautocomplete="email"pour que Chrome puisse remplir automatiquement l'adresse et la reconnaître. - Attributs du champ de jeton :
- Définissez
autocomplete="email-verification-token": Chrome identifie ce champ pour renseigner le jeton lors de l'envoi. - Définissez
nonce="<VALUE>": le site doit fournir un nonce unique lié à la session pour valider l'envoi du formulaire.
- Définissez
Valider le jeton de validation de l'adresse e-mail (EVT)
Lorsque l'utilisateur envoie le formulaire, votre serveur reçoit l'adresse e-mail et le jeton du champ masqué. Un champ de jeton vide indique que le navigateur ou le fournisseur ne sont pas compatibles avec la validation étendue, ou que l'utilisateur a ignoré la validation. Si cela se produit, revenez à votre procédure de validation existante, par exemple en envoyant un code secret à usage unique ou un lien magique.
Si un jeton est présent, validez-le comme suit :
- Analysez le jeton à l'aide d'une bibliothèque SD-JWT.
- Validez les valeurs attendues et les revendications de session.
- Vérifiez la délégation DNS.
- Découvrez les métadonnées de l'émetteur et récupérez les JWKS.
- Vérifiez les signatures cryptographiques et l'association de clés.
1. Analyser le jeton
Le jeton utilise le format RFC 9901 : Selective Disclosure JWT (SD-JWT+KB). Utilisez les bibliothèques appropriées pour votre plate-forme afin d'analyser et de valider le jeton.
Par exemple, pour Node, vous pouvez utiliser @sd-jwt/core et jose. Sous sa forme brute, il se présente comme suit : un jeton JWT signé par un émetteur, suivi d'une ou plusieurs divulgations, et se terminant par un jeton JWT de liaison de clé, chaque composant étant séparé par un tilde :
<Issuer-signed EVT>~<Disclosure 1>~...~<Disclosure N>~<Key Binding JWT>
Dans l'implémentation actuelle, le jeton ne contient aucune divulgation (<Issuer-signed EVT>~<Key Binding JWT>). Toutefois, cela peut changer à l'avenir.
Décodez le jeton avec la bibliothèque :
import { decodeSdJwtSync } from "@sd-jwt/core";
import { createHash } from "node:crypto";
const hasher = (data, alg) =>
createHash(alg === "sha-256" ? "sha256" : alg)
.update(data)
.digest();
const decoded = decodeSdJwtSync(rawToken, hasher);
const evtPayload = decoded.jwt.payload;
const kbPayload = decoded.kbJwt?.payload;
Si verifier.example valide demo@provider.example, le jeton décodé ressemble à ce qui suit :
{
"evtJwtDecodedHeader": {
"typ": "evt+jwt",
"alg": "EdDSA",
"kid": "issuer-key-id"
},
"evtJwtDecodedPayload": {
"iss": "https://provider.example",
"iat": 12345678901,
"exp": 12345679901,
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "pUbLiCkEy123pUbLiCkEy123pUbLiCkEy123"
}
},
"email": "demo@provider.example",
"email_verified": true
},
"kbJwtDecodedHeader": {
"alg": "EdDSA",
"typ": "kb+jwt"
},
"kbJwtDecodedPayload": {
"aud": "https://verifier.example",
"iat": 12345678901,
"nonce": "rAnDoM123rAnDoM123rAnDoM123rAnDoM123",
"sd_hash": "hAsH456hAsH456hAsH456hAsH456hAsH456"
},
"disclosures": []
}
2. Valider les valeurs attendues et les revendications de session
Vérifiez que les valeurs de base de la charge utile correspondent aux valeurs que vous avez fournies et attendues :
email_verified: doit êtretrue.email: doit correspondre à l'adresse e-mail indiquée dans le formulaire.aud(audience) : doit correspondre à l'origine de votre site.nonce: doit correspondre au nonce fourni dans votre formulaire.iat(émis le) etexp(expiration) : vérifiez que le jeton est dans sa période de validité et qu'il n'a pas expiré.
3. Vérifier la délégation DNS
Vérifiez l'enregistrement DNS _email-verification pour le domaine de l'adresse e-mail. Par exemple, pour demo@gmail.com, interrogez l'enregistrement TXT _email-verification.gmail.com. Pour ce fournisseur, la requête renvoie l'emplacement du fournisseur de compte, c'est-à-dire accounts.google.com.
$ dig +short TXT _email-verification.gmail.com
"iss=accounts.google.com"
Vérifiez que le schéma de l'émetteur est https:// et que https://<domain> correspond à la revendication iss dans l'EVT.
4. Vérifier la signature EVT
Récupérez les métadonnées de découverte de l'émetteur à partir de https://<issuer>/.well-known/email-verification :
{
"issuance_endpoint": "https://accounts.google.com/gsi/email-verification/issue",
"jwks_uri": "https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks",
"signing_alg_values_supported": ["EdDSA"]
}
Récupérez le jeu de clés Web JSON à partir de jwks_uri.
Utilisez votre bibliothèque SD-JWT pour valider le package de jetons. La bibliothèque coordonne la validation :
- Validation de la signature de l'émetteur sur l'EVT par rapport aux JWKS récupérés.
- Valider la signature du navigateur sur le KB-JWT à l'aide de la clé publique éphémère dans
cnf.jwk. - Vérification de la liaison de clé (
aud,nonceet hachage du résumésd_hash).
Exemple de logique de validation dans Node.js :
import { SDJwtInstance } from "@sd-jwt/core";
import { importJWK, compactVerify } from "jose";
import { createHash } from "node:crypto";
const hasher = (data, alg) =>
createHash(alg === "sha-256" ? "sha256" : alg)
.update(data)
.digest();
const sdJwt = new SDJwtInstance({ hasher });
sdJwt.config({
hasher,
// Verifier for the Issuer-signed EVT
verifier: async (data, sig) => {
const token = `${data}.${sig}`;
const header = decoded.jwt.header;
const headerAlg = header.alg || "ES256";
// Match by kid if present, or iterate across matching algorithm keys
const keysToTry = header.kid
? jwksData.keys.filter(k => k.kid === header.kid)
: jwksData.keys;
for (const jwk of keysToTry) {
try {
const pubKey = await importJWK(jwk, jwk.alg || headerAlg);
await compactVerify(token, pubKey);
return true;
} catch {
// Try next candidate key
}
}
return false;
},
// Verifier for the Key Binding JWT (KB-JWT)
kbVerifier: async (data, sig) => {
try {
const browserJwkKey = evtPayload.cnf?.jwk;
if (!browserJwkKey) return false;
const pubKey = await importJWK(browserJwkKey, decoded.kbJwt.header.alg || "ES256");
await compactVerify(`${data}.${sig}`, pubKey);
return true;
} catch {
return false;
}
},
});
// The library automatically verifies EVT signature, KB-JWT signature, audience, nonce, and sd_hash
const result = await sdJwt.verify(rawToken, {
kb: {
expectedNonce: sessionNonce,
expectedAudience: "https://example.com",
required: true,
},
});
const verifiedPayload = result.payload;
Si toutes les étapes aboutissent, vous avez validé l'adresse e-mail auprès du fournisseur. Si la validation échoue, revenez à l'envoi d'un e-mail de confirmation à l'utilisateur à l'aide de votre flux habituel.
Dépannage
Si la validation échoue ou si le navigateur ne fournit pas de jeton, vérifiez les problèmes courants suivants :
Le champ de jeton est vide lors de l'envoi
- Inscription à l'essai Origin Trial : vérifiez que l'en-tête
Origin-Trialou la balise<meta>sont diffusés sur la page. Pour les essais Origin Trial tiers (Chrome 154 et versions ultérieures), l'origine de l'essai Origin Trial enregistré doit être de même origine que l'émetteur (https://<issuer-domain>). Vous pouvez inspecter la configuration de l'essai Origin Trial sur un site dans les Outils de développement sous Application > Frames > (sélectionnez le frame concerné) > Origin trials. - Balises de formulaire :
<input type="email" autocomplete="email">et<input type="hidden" autocomplete="email-verification-token" nonce="...">doivent se trouver dans le même élément<form>(et non isolés au-delà des limites du Shadow DOM), etnoncene doit pas être vide. - Envoi anticipé ou page réutilisée : le navigateur récupère le jeton en arrière-plan après la saisie ou l'autosaisie de l'adresse e-mail. Si vous envoyez la requête avant qu'elle ne soit terminée, le jeton reste vide. Cela peut se produire si l'utilisateur appuie sur "Retour" pour envoyer le formulaire après avoir saisi son adresse e-mail.
- Conditions préalables concernant le navigateur et le fournisseur : l'utilisateur doit être connecté à un fournisseur participant dans le même profil de navigateur et avoir activé l'adresse e-mail validée dans les paramètres Chrome (
chrome://settings/contactInfo).
Échec de la validation de la signature de l'émetteur
- En-tête
kidmanquant : la revendicationkid(ID de clé) dans l'en-tête EVT et le JWKS est facultative (par exemple, Gmail ometkid). Sikidest absent, parcourez toutes les clés candidates dans lejwks_uride l'émetteur au lieu d'échouer lors d'une recherche d'ID de clé. - Identifiants d'algorithme (EdDSA et Ed25519) : les émetteurs et les bibliothèques peuvent spécifier
EdDSAouEd25519(avecES256). Assurez-vous que votre logique d'importation et de validation JWK accepte les deux identifiants. - Format d'origine de l'émetteur (
iss) : l'enregistrement TXT DNS (_email-verification.<domain>) contient un nom d'hôte nu (iss=accounts.issuer.example), tandis que la revendication EVTissest une origine HTTPS complète (https://accounts.issuer.example, sans barre oblique à la fin). Ajoutez le préfixehttps://à la valeur de l'enregistrement DNS avant de la comparer.
Échec de la validation de la liaison de clé (KB-JWT)
- Nonce non concordant ou expiré : assurez-vous que le
nonceaffiché dans<input>correspond au nonce de session actif sur votre serveur et qu'il n'a pas été remplacé par un autre onglet ni utilisé par une requête précédente. - Incohérence de l'audience (
aud) : la revendicationaudcorrespond à l'origine HTTPS du validateur (https://verifier.example, sans chemin d'accès ni barre oblique de fin).
Échec de la comparaison de la revendication par e-mail (email)
- Casse et canonisation : Chrome 156 et versions ultérieures renvoient la revendication
emailoctet par octet telle qu'elle a été saisie dans le formulaire, mais les versions antérieures du navigateur ou les fournisseurs peuvent renvoyer une adresse canonisée (par exemple,First.Last@example.compourfirst.last@example.com). Utilisez une comparaison insensible à la casse lorsque vous faites correspondre la revendicationemaildu jeton à la valeur du formulaire envoyé.