Pour en savoir plus, vous pouvez parcourir le code de démonstration du fournisseur d'adresse e-mail fictive et consulter les étapes de l'émetteur dans les propositions d'API Email Verification et de protocole Email Verification.
En tant qu'émetteur, vous n'avez pas besoin de vous inscrire à l'essai Origin Trial ni de fournir de jeton, car le site de la partie de confiance déclenche le comportement du navigateur. Assurez-vous que vos points de terminaison sont configurés pour répondre à ces requêtes.
Configurer la découverte de l'émetteur
Pour permettre aux navigateurs de découvrir automatiquement vos points de terminaison de validation lorsqu'une adresse e-mail appartenant à votre domaine est sélectionnée, exposez votre configuration à l'aide du DNS et d'un point de terminaison HTTP .well-known.
Configurer un enregistrement de délégation DNS
Configurez un enregistrement TXT DNS sur votre domaine de messagerie qui délègue l'autorité de validation à votre identifiant d'émetteur. Ces identifiants peuvent utiliser le même domaine en fonction de votre infrastructure.
Format de l'enregistrement : _email-verification.<email-domain>
Exemple de fichier de zone :
_email-verification.example.com IN TXT "iss=accounts.issuer.example"
Héberger un point de terminaison .well-known/email-verification
Hébergez un fichier de métadonnées JSON sur le domaine de votre émetteur, sous le chemin d'accès /.well-known/.
Ce fichier décrit vos capacités d'émission et les algorithmes de signature cryptographique compatibles avec votre infrastructure.
Point de terminaison : https://<issuer-domain>/.well-known/email-verification
Exemple de réponse :
{
"issuance_endpoint": "https://accounts.issuer.example/email-verification/issuance",
"jwks_uri": "https://accounts.issuer.example/.well-known/vc-public-jwks",
"signing_alg_values_supported": ["EdDSA", "ES256"]
}
Héberger un point de terminaison .well-known/web-identity
Vous avez peut-être déjà implémenté une ressource JSON .well-known supplémentaire dans le cadre de l'API Federated Credentials (FedCM). Cette ressource fournit des liens vers le point de terminaison de vos comptes et l'URL de connexion.
Point de terminaison : https://<domain>/.well-known/web-identity
Exemple de réponse :
{
"accounts_endpoint": "https://accounts.issuer.example/accounts",
"login_url": "https://accounts.issuer.example/login"
}
Utiliser un point de terminaison de comptes
Le point de terminaison "accounts" de l'API FedCM fournit actuellement une liste des comptes connectés. L'exemple suivant montre une réponse minimale. Pour en savoir plus, consultez le guide d'implémentation du fournisseur d'identité.
Point de terminaison : tel que spécifié dans .well-known/web-identity
Voici un exemple de réponse :
{
"accounts": [
{
"id": "demo-example",
"name": "Demo User",
"email": "demo@example.com",
"given_name": "Demo"
}
]
}
Intégrer l'API Login Status
L'utilisateur doit avoir une session active avec le fournisseur, et vous devez le signaler au navigateur à l'aide de l'API Login Status.
Lorsqu'un utilisateur se connecte ou se déconnecte, diffusez l'en-tête de réponse HTTP correspondant :
Set-Login: logged-in
Set-Login: logged-out
Vous pouvez également mettre à jour l'état à l'aide de JavaScript dans le contexte de votre application Web :
navigator.login.setStatus("logged-in");
navigator.login.setStatus("logged-out");
Traiter les demandes d'émission
Votre issuance_endpoint reçoit une requête application/json POST contenant la clé email et les en-têtes Signatures de message HTTP pour Signature, Signature-Input et Signature-Key.
Utilisez une bibliothèque compatible avec les en-têtes structurés et les signatures de messages HTTP pour votre environnement. Dans Node.js, vous pouvez utiliser structured-headers et http-message-sig.
Format complet de la demande :
POST /email-verification/issuance HTTP/1.1
Host: provider.example
Accept: application/json
Content-Digest: sha-256=:aBc123aBc123aBc123aBc123aBc123=:
Content-Type: application/json
Sec-Fetch-Dest: email-verification
Signature: sig=:+dEf567dEf567/dEf567dEf567dEf567/dEf567==:
Signature-Input: sig=("@method" "@authority" "@path" "content-digest" "signature-key");created=1786455840
Signature-Key: sig=hwk;crv="Ed25519";kty="OKP";x="gHi890_gHi890_gHi890"
{email: "demo@example.com"}
Analysez et validez la requête :
- Authentification de session : validez vos cookies de session propriétaires envoyés avec la requête. L'utilisateur doit être authentifié.
- En-tête
Sec-Fetch-Dest: défini suremail-verification. - Signatures de messages HTTP : validez la signature de la requête à l'aide de la clé publique éphémère dans
Signature-Keyet validezContent-Digest. - Charge utile : le corps JSON contient la chaîne
emailrequise pour la validation.
Réponse à l'émission
Une fois la session et le jeton de requête validés, générez un JWT de divulgation sélective signé (SD-JWT) renvoyé au format JSON à l'aide des bibliothèques appropriées pour votre plate-forme. Par exemple, pour Node, vous pouvez utiliser @sd-jwt/core et jose.
Le format de la charge utile brute doit ressembler à ceci :
{
"iss": "https://accounts.issuer.example",
"iat": 1780272000,
"exp": 1780272300,
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "pUbLiCKeY123pUbLiCKeY123pUbLiCKeY123",
"y": "pUbLiCKeY456pUbLiCKeY456pUbLiCKeY456"
}
},
"email": "demo@example.com",
"email_verified": true
}
Créez, signez et renvoyez le jeton :
import { importJWK, CompactSign } from "jose";
import { SDJwtInstance } from "@sd-jwt/core";
import crypto from "node:crypto";
// Issuer private key from secure storage
const privateKey = await importJWK(PRIVATE_KEY_JWK, "EdDSA");
const origin = url.origin;
const currentTime = Math.floor(Date.now() / 1000);
const evtPayload = {
iss: origin,
iat: currentTime,
exp: currentTime + 300, // 5 minutes
cnf: {
jwk: browserJwk,
},
email: payload.email, // exactly as received in payload
email_verified: true,
};
const sdJwt = new SDJwtInstance({
signer: async (data) => {
const [headerB64, payloadB64] = data.split(".");
const header = JSON.parse(Buffer.from(headerB64, "base64url").toString());
const payload = Buffer.from(payloadB64, "base64url");
const signed = await new CompactSign(payload).setProtectedHeader(header).sign(privateKey);
return signed.split(".").pop()!;
},
signAlg: "EdDSA",
hasher: async (data, alg) => {
const nodeAlg = alg.replace("-", "");
return new Uint8Array(crypto.createHash(nodeAlg).update(data).digest());
},
hashAlg: "sha-256",
saltGenerator: async () => crypto.randomBytes(16).toString("base64url"),
});
const issuanceToken = await sdJwt.issue(evtPayload, undefined, {
header: {
alg: "EdDSA",
kid: PRIVATE_KEY_JWK.kid,
typ: "evt+jwt",
},
});
return sendResponse({ issuance_token: issuanceToken, }, 200);
Le corps de la réponse ressemble à ceci :
{
"issuance_token": "tOkEn123tOkEn123tOkEn123...~"
}
Une fois que vous avez créé et signé le jeton de validation de l'adresse e-mail, le navigateur le transmet au site de validation.
Dépannage
Si le navigateur ne contacte pas vos points de terminaison ou rejette les jetons émis, vérifiez les problèmes courants suivants :
Le navigateur n'appelle jamais accounts_endpoint ni issuance_endpoint.
- État de connexion non défini : Chrome n'interroge vos points de terminaison que s'il sait que l'utilisateur est connecté. Assurez-vous que votre flux de connexion définit l'en-tête HTTP
Set-Login: logged-inou appellenavigator.login.setStatus("logged-in"). - Cookies de session bloqués (
SameSite=None) : le navigateur récupère vosaccounts_endpointetissuance_endpointauprès d'une partie de confiance sur un autre site. Comme il s'agit de requêtes multisites, votre cookie de session doit inclureSameSite=None; Secure. Un cookieSameSite=Laxfonctionne lors des tests sur un outil de validation du même site, mais est omis dans les requêtes multisites. - Problème de découverte ou d'incompatibilité de compte : vérifiez que
_email-verification.<email-domain>renvoie un seul enregistrement TXT (iss=<issuer-domain>, sanshttps://), que les deux points de terminaison.well-knownrenvoientContent-Type: application/jsonet que la réponseaccounts_endpointinclut un compte dont leemailcorrespond à l'adresse saisie.
Échec de la validation de la demande d'émission
- Orthographe de l'en-tête
Sec-Fetch-Dest: Chrome 154 et versions ultérieures envoientSec-Fetch-Dest: email-verification(avec un tiret), tandis que Chrome 153 envoyaitemailverification(sans tiret). Acceptez les deux valeurs lors du déploiement. - Incompatibilité de la signature de message HTTP (
@authority) derrière un proxy : lors de la vérification de la signature RFC 9421, le composant@authorityreflète l'hôte public. Si votre serveur se trouve derrière un équilibreur de charge ou un proxy inverse, reconstruisez l'URL de validation à l'aide deX-Forwarded-Host(ou de votre origine publique) plutôt que du nom d'hôte interne, et calculezContent-Digestsur les octets bruts du corps de la requête avant l'analyse JSON.
Le navigateur rejette le issuance_token renvoyé.
- Revendication
emailmodifiée ou canonique : à partir de Chrome 156, le navigateur vérifie que la revendicationemailEVT correspond à la revendicationemaildemandée octet par octet. Si votre backend normalise l'adresse dans un format de compte canonique (par exemple, en renvoyantFirst.Last@example.comlorsquefirst.LAST@example.coma été demandé), Chrome supprime le jeton. Fais correspondre la demande au compte de l'utilisateur, mais renvoie la chaîneemailexacte reçue dans le corps de la demande. - Tilde (
~) manquant à la fin : même en l'absence de divulgations,issuance_tokendoit être un SD-JWT valide se terminant par un tilde (<Issuer-signed-JWT>~) afin que le navigateur puisse ajouter<KB-JWT>. issoucnf.jwknon concordants : assurez-vous que la revendicationissEVT correspond exactement à l'origine HTTPS (https://<issuer-domain>, sans barre oblique de fin) correspondant à vos métadonnées.well-known/email-verification, et quecnf.jwkintègre la clé publique éphémère du navigateur à partir de l'en-têteSignature-Key.