Implementierung der vertrauenden Partei

Wenn Sie die E-Mail-Bestätigung auf Ihrer Website implementieren möchten, müssen Sie das Formular-Markup aktualisieren, um das Token anzufordern, und die serverseitige Validierung für eingehende Tokens hinzufügen.

Für den Ursprungstest registrieren

Für die Bestätigung von Websites muss das Origin Trial auf der Website konfiguriert sein.

Ab Chrome 154 werden Ursprungstests von Drittanbietern unterstützt. Es gibt jedoch eine wichtige Einschränkung: Der registrierte Ursprung für den Test muss mit dem Aussteller auf derselben Website sein. Beispiel:

  • Ausstellerdomain: issuer.example
  • OT-Registrant: https://issuer.example
  • JavaScript-Quelle: https://issuer.example (oder https://app.issuer.example mit Subdomain-Abgleich)

Formularfelder konfigurieren

Fügen Sie Ihrem E‑Mail-Übermittlungsformular ein ausgeblendetes Tokenfeld hinzu:

<input
  type="email"
  name="email-address"
  autocomplete="email">
<input
  type="hidden"
  name="token"
  autocomplete="email-verification-token"
  nonce="rAnD0m-VaLuE">

Feldanforderungen:

  • E-Mail-Feld: Legen Sie type="email" und autocomplete="email" fest, damit Chrome die Adresse automatisch ausfüllen und erkennen kann.
  • Attribute für Tokenfelder:
    • autocomplete="email-verification-token" festlegen: Chrome identifiziert dieses Feld, um das Token bei der Übermittlung einzufügen.
    • nonce="<VALUE>" festlegen: Die Website muss einen eindeutigen sitzungsgebundenen Nonce zur Bestätigung der Formulareinreichung bereitstellen.

E‑Mail-Bestätigungstoken (EVT) validieren

Wenn der Nutzer das Formular sendet, empfängt Ihr Server die E‑Mail-Adresse und das Token aus dem verborgenen Feld. Ein leeres Tokenfeld bedeutet, dass der Browser oder Anbieter EVP nicht unterstützt oder der Nutzer die Bestätigung übersprungen hat. Wenn dies der Fall ist, greifen Sie auf Ihr bestehendes Bestätigungsverfahren zurück, z. B. das Senden eines Einmalpassworts oder eines Magic-Links.

Wenn ein Token vorhanden ist, validieren Sie es so:

  1. Parsen Sie das Token mit einer SD-JWT-Bibliothek.
  2. Erwartete Werte und Sitzungsansprüche validieren
  3. DNS-Delegierung prüfen
  4. Aussteller-Metadaten ermitteln und JWKS abrufen
  5. Kryptografische Signaturen und Schlüsselbindung prüfen

1. Token parsen

Das Token verwendet das Format RFC 9901: Selective Disclosure JWT (SD-JWT+KB). Verwenden Sie die entsprechenden Bibliotheken für Ihre Plattform, um das Token zu parsen und zu validieren. Für Node können Sie beispielsweise @sd-jwt/core und jose verwenden. In seiner Rohform sieht das so aus: ein vom Aussteller signiertes JWT, gefolgt von null oder mehr Offenlegungen und einem Key Binding JWT. Die einzelnen Komponenten sind durch eine Tilde getrennt:

<Issuer-signed EVT>~<Disclosure 1>~...~<Disclosure N>~<Key Binding JWT>

In der aktuellen Implementierung enthält das Token keine Offenlegungen (<Issuer-signed EVT>~<Key Binding JWT>). Dies kann sich jedoch in Zukunft ändern.

Decodieren Sie das Token mit der Bibliothek:

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;

Wenn verifier.example demo@provider.example verifiziert, sieht das decodierte Token in etwa so aus:

{
  "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. Erwartete Werte und Sitzungsansprüche validieren

Prüfen Sie, ob die grundlegenden Werte in der Nutzlast mit den von Ihnen angegebenen und erwarteten Werten übereinstimmen:

  • email_verified: Muss true sein.
  • email: Muss mit der im Formular angegebenen E-Mail-Adresse übereinstimmen.
  • aud (audience): Muss mit dem Ursprung Ihrer Website übereinstimmen.
  • nonce: Muss mit dem im Formular angegebenen Nonce übereinstimmen.
  • iat (Ausstellungsdatum) und exp (Ablaufdatum): Prüfen Sie, ob das Token innerhalb des gültigen Zeitfensters liegt und nicht abgelaufen ist.

3. DNS-Delegierung prüfen

Bestätigen Sie den _email-verification-DNS-Eintrag für die Domain der E‑Mail-Adresse. Fragen Sie beispielsweise für demo@gmail.com den TXT-Eintrag _email-verification.gmail.com ab. Für diesen Anbieter gibt die Abfrage den Standort des Kontoanbieters zurück, also accounts.google.com.

$ dig +short TXT _email-verification.gmail.com
"iss=accounts.google.com"

Prüfen Sie, ob das Ausstellerschema https:// ist und ob https://<domain> mit dem iss-Anspruch im EVT übereinstimmt.

4. EVT-Signatur überprüfen

Rufen Sie die Discovery-Metadaten des Ausstellers unter https://<issuer>/.well-known/email-verification ab:

{
  "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"]
}

Rufen Sie das JSON Web Key Set von jwks_uri ab.

Verwenden Sie Ihre SD-JWT-Bibliothek, um das Tokenpaket zu überprüfen. Die Bibliothek koordiniert die Validierung:

  1. Die Ausstellersignatur für das EVT wird anhand der abgerufenen JWKS validiert.
  2. Die Signatur des Browsers im KB-JWT wird mit dem temporären öffentlichen Schlüssel in cnf.jwk validiert.
  3. Die Schlüsselbindung wird geprüft (aud, nonce und der Digest-Hash sd_hash).

Beispiel für die Bestätigungslogik in 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;

Wenn alle Schritte erfolgreich sind, haben Sie die E‑Mail-Adresse beim Anbieter bestätigt. Wenn die Bestätigung fehlschlägt, senden Sie dem Nutzer eine Bestätigungs-E‑Mail über Ihren normalen Ablauf.

Fehlerbehebung

Wenn die Bestätigung fehlschlägt oder der Browser kein Token bereitstellt, prüfen Sie die folgenden häufigen Probleme:

Das Token-Feld ist bei der Einreichung leer

  • Registrierung für den Ursprungstest: Prüfen Sie, ob der Origin-Trial-Header oder das <meta>-Tag auf der Seite bereitgestellt wird. Bei Drittanbieter-Testzeiträumen (Chrome 154 und höher) muss der registrierte Testzeitraum-Ursprung derselben Website wie der Aussteller (https://<issuer-domain>) sein. Sie können die Testzeitraum-Konfiguration auf einer Website in den DevTools unter Anwendung > Frames > (relevanten Frame auswählen) > Testzeiträume prüfen.
  • Formular-Markup: Sowohl <input type="email" autocomplete="email"> als auch <input type="hidden" autocomplete="email-verification-token" nonce="..."> müssen sich im selben <form>-Element befinden (nicht isoliert über Shadow-DOM-Grenzen hinweg) und nonce darf nicht leer sein.
  • Vorzeitige Übermittlung oder wiederverwendete Seite: Der Browser ruft das Token im Hintergrund ab, nachdem die E‑Mail-Adresse eingegeben oder automatisch ausgefüllt wurde. Wenn Sie das Formular vor Abschluss der Anfrage einreichen, bleibt das Token leer. Das kann passieren, wenn der Nutzer nach der Eingabe seiner E‑Mail-Adresse die Eingabetaste drückt, um das Formular zu senden.
  • Voraussetzungen für Browser und Anbieter: Der Nutzer muss im selben Browserprofil bei einem teilnehmenden Anbieter angemeldet sein und Bestätigte E-Mail-Adresse muss in den Chrome-Einstellungen (chrome://settings/contactInfo) aktiviert sein.

Überprüfung der Ausstellersignatur fehlgeschlagen

  • Fehlender kid-Header: Die kid-Anforderung (Schlüssel-ID) im EVT-Header und JWKS ist optional (z. B. wird kid in Gmail ausgelassen). Wenn kid fehlt, durchlaufen Sie alle in der jwks_uri des Ausstellers infrage kommenden Schlüssel, anstatt bei einer Schlüssel-ID-Suche einen Fehler auszugeben.
  • Algorithmuskennungen (EdDSA und Ed25519): Aussteller und Bibliotheken können entweder EdDSA oder Ed25519 (neben ES256) angeben. Achten Sie darauf, dass Ihre JWK-Import- und ‑Prüflogik beide Kennungen akzeptiert.
  • Format des Ursprungs des Ausstellers (iss): Der DNS-TXT-Eintrag (_email-verification.<domain>) enthält einen einfachen Hostnamen (iss=accounts.issuer.example), während der Anspruch iss des EVT ein vollständiger HTTPS-Ursprung (https://accounts.issuer.example, ohne abschließenden Schrägstrich) ist. Stellen Sie dem DNS-Eintragswert vor dem Vergleich das Präfix https:// voran.

Validierung der Schlüsselbindung (KB-JWT) fehlgeschlagen

  • Nicht übereinstimmende oder abgelaufene Nonce: Achten Sie darauf, dass die in <input> gerenderte nonce mit der aktiven Sitzungs-Nonce auf Ihrem Server übereinstimmt und nicht durch einen anderen Tab überschrieben oder durch eine vorherige Anfrage verwendet wurde.
  • Nicht übereinstimmende Zielgruppe (aud): Der aud-Anspruch ist der HTTPS-Ursprung des Prüfers (https://verifier.example, ohne Pfad oder abschließenden Schrägstrich).

Vergleich der E-Mail-Anforderung (email) fehlgeschlagen

  • Groß-/Kleinschreibung und Kanonisierung: In Chrome 156 und höher wird die email-Anforderung bytegenau wie im Formular eingegeben zurückgegeben. In früheren Browserversionen oder bei Anbietern kann jedoch eine kanonisierte Adresse zurückgegeben werden (z. B. First.Last@example.com für first.last@example.com). Verwenden Sie einen Vergleich, bei dem die Groß-/Kleinschreibung nicht berücksichtigt wird, wenn Sie die email-Anforderung des Tokens mit dem im Formular eingereichten Wert abgleichen.