Implémentation du fournisseur de messagerie (émetteur)

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 sur email-verification.
  • Signatures de messages HTTP : validez la signature de la requête à l'aide de la clé publique éphémère dans Signature-Key et validez Content-Digest.
  • Charge utile : le corps JSON contient la chaîne email requise 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-in ou appelle navigator.login.setStatus("logged-in").
  • Cookies de session bloqués (SameSite=None) : le navigateur récupère vos accounts_endpoint et issuance_endpoint auprès d'une partie de confiance sur un autre site. Comme il s'agit de requêtes multisites, votre cookie de session doit inclure SameSite=None; Secure. Un cookie SameSite=Lax fonctionne 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>, sans https://), que les deux points de terminaison .well-known renvoient Content-Type: application/json et que la réponse accounts_endpoint inclut un compte dont le email correspond à 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 envoient Sec-Fetch-Dest: email-verification (avec un tiret), tandis que Chrome 153 envoyait emailverification (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 @authority reflè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 de X-Forwarded-Host (ou de votre origine publique) plutôt que du nom d'hôte interne, et calculez Content-Digest sur les octets bruts du corps de la requête avant l'analyse JSON.

Le navigateur rejette le issuance_token renvoyé.

  • Revendication email modifiée ou canonique : à partir de Chrome 156, le navigateur vérifie que la revendication email EVT correspond à la revendication email demandée octet par octet. Si votre backend normalise l'adresse dans un format de compte canonique (par exemple, en renvoyant First.Last@example.com lorsque first.LAST@example.com a été demandé), Chrome supprime le jeton. Fais correspondre la demande au compte de l'utilisateur, mais renvoie la chaîne email exacte reçue dans le corps de la demande.
  • Tilde (~) manquant à la fin : même en l'absence de divulgations, issuance_token doit être un SD-JWT valide se terminant par un tilde (<Issuer-signed-JWT>~) afin que le navigateur puisse ajouter <KB-JWT>.
  • iss ou cnf.jwk non concordants : assurez-vous que la revendication iss EVT correspond exactement à l'origine HTTPS (https://<issuer-domain>, sans barre oblique de fin) correspondant à vos métadonnées .well-known/email-verification, et que cnf.jwk intègre la clé publique éphémère du navigateur à partir de l'en-tête Signature-Key.