Implementazione della relying party

Per implementare la verifica email sul tuo sito, aggiorna il markup del modulo per richiedere il token e aggiungi la convalida lato server per i token in arrivo.

Registrati per l'origin trial

I siti di verifica devono avere l'origin trial configurato sul proprio sito.

A partire da Chrome 154, le prove di origine di terze parti sono supportate con un avviso importante: l'origine registrata per la prova deve essere dello stesso sito dell'emittente. Ad esempio:

  • Dominio dell'emittente: issuer.example
  • OT registrant: https://issuer.example
  • Origine JavaScript: https://issuer.example (o https://app.issuer.example con corrispondenza dei sottodomini)

Configurare i campi del modulo

Aggiungi un campo token nascosto al modulo di invio email:

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

Requisiti dei campi:

  • Campo Email: imposta type="email" e autocomplete="email" in modo che Chrome possa compilare automaticamente e riconoscere l'indirizzo.
  • Attributi del campo token:
    • Imposta autocomplete="email-verification-token": Chrome identifica questo campo per compilare il token al momento dell'invio.
    • Imposta nonce="<VALUE>": il sito deve fornire un nonce univoco associato alla sessione per verificare l'invio del modulo.

Convalida il token di verifica email (EVT)

Quando l'utente invia il modulo, il tuo server riceve l'indirizzo email e il token dal campo nascosto. Un campo token vuoto indica che il browser o il fornitore non supporta la verifica dell'identità con documento oppure che l'utente ha saltato la verifica. In questo caso, torna alla procedura di verifica esistente, ad esempio l'invio di una password usa e getta o di un link magico.

Se è presente un token, convalidalo nel seguente modo:

  1. Analizza il token utilizzando una libreria SD-JWT.
  2. Convalida i valori previsti e le rivendicazioni delle sessioni.
  3. Verifica la delega DNS.
  4. Scopri i metadati dell'emittente e recupera JWKS.
  5. Verifica le firme crittografiche e l'associazione delle chiavi.

1. Analizza il token

Il token utilizza il formato RFC 9901: Selective Disclosure JWT (SD-JWT+KB). Utilizza le librerie appropriate per la tua piattaforma per analizzare e convalidare il token. Ad esempio, per Node puoi utilizzare @sd-jwt/core e jose. Nella sua forma non elaborata, si presenta così: un JWT firmato dall'emittente, seguito da zero o più divulgazioni e terminante con un JWT di associazione della chiave, con ogni componente separato da una tilde:

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

Nell'implementazione attuale, il token contiene zero divulgazioni (<Issuer-signed EVT>~<Key Binding JWT>). Tuttavia, questo potrebbe cambiare in futuro.

Decodifica il token con la libreria:

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;

Se verifier.example verifica demo@provider.example, il token decodificato è simile al seguente:

{
  "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. Convalidare i valori previsti e le rivendicazioni delle sessioni

Controlla che i valori di base nel payload corrispondano ai valori forniti e previsti:

  • email_verified: deve essere true.
  • email: deve corrispondere all'indirizzo email inviato nel modulo.
  • aud (pubblico): deve corrispondere all'origine del tuo sito.
  • nonce: deve corrispondere al nonce fornito nel modulo.
  • iat (emesso il) e exp (scadenza): verifica che il token rientri nella finestra temporale valida e non sia scaduto.

3. Verifica la delega DNS

Verifica il record DNS _email-verification per il dominio dell'indirizzo email. Ad esempio, per demo@gmail.com, esegui una query sul record TXT _email-verification.gmail.com. Per questo fornitore, la query restituisce la posizione del fornitore dell'account, ovvero accounts.google.com.

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

Verifica che lo schema dell'emittente sia https:// e che https://<domain> corrisponda all'attestazione iss nell'EVT.

4. Verificare la firma EVT

Recupera i metadati di rilevamento dell'emittente da 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"]
}

Recupera il set di chiavi web JSON da jwks_uri.

Utilizza la libreria SD-JWT per verificare il pacchetto di token. La libreria coordina la convalida:

  1. Convalida della firma dell'emittente sull'EVT rispetto ai JWKS recuperati.
  2. Convalida della firma del browser sul KB-JWT utilizzando la chiave pubblica effimera in cnf.jwk.
  3. Verifica dell'associazione della chiave (aud, nonce e hash del digest sd_hash).

Esempio di logica di verifica 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;

Se tutti i passaggi vanno a buon fine, l'indirizzo email è stato verificato rispetto al provider. Se la verifica non va a buon fine, torna all'invio di un'email di conferma all'utente utilizzando il flusso normale.

Risoluzione dei problemi

Se la verifica non va a buon fine o il browser non fornisce un token, controlla i seguenti problemi comuni:

Il campo Token è vuoto al momento dell'invio

  • Registrazione all'origin trial: conferma che l'intestazione Origin-Trial o il tag <meta> venga pubblicato nella pagina. Per le prove dell'origine di terze parti (Chrome 154 e versioni successive), l'origine della prova registrata deve essere dello stesso sito dell'emittente (https://<issuer-domain>). Puoi ispezionare la configurazione della prova dell'origine su un sito in DevTools in Applicazione > Frame > (seleziona il frame pertinente) > Prove dell'origine.
  • Markup del modulo: sia <input type="email" autocomplete="email"> che <input type="hidden" autocomplete="email-verification-token" nonce="..."> devono trovarsi nello stesso elemento <form> (non isolati tra i limiti del DOM ombra) e nonce non deve essere vuoto.
  • Invio anticipato o pagina riutilizzata: il browser recupera il token in background dopo l'inserimento o il riempimento automatico dell'email. L'invio prima del completamento della richiesta lascia il token vuoto. Ciò può accadere se l'utente preme Invio per inviare il modulo dopo aver inserito la sua email.
  • Prerequisiti per browser e provider: l'utente deve aver eseguito l'accesso a un provider partecipante nello stesso profilo del browser e aver attivato l'opzione Email verificata nelle impostazioni di Chrome (chrome://settings/contactInfo).

La verifica della firma dell'emittente non riesce

  • Intestazione kid mancante: l'attestazione kid (ID chiave) nell'intestazione EVT e JWKS è facoltativa (ad esempio, Gmail omette kid). Se kid è assente, itera tutte le chiavi candidate nel jwks_uri dell'emittente anziché non riuscire a trovare un ID chiave.
  • Identificatori di algoritmi (EdDSA ed Ed25519): gli emittenti e le librerie possono specificare EdDSA o Ed25519 (insieme a ES256). Assicurati che la logica di importazione e verifica JWK accetti entrambi gli identificatori.
  • Formato dell'origine dell'emittente (iss): il record TXT DNS (_email-verification.<domain>) contiene un nome host semplice (iss=accounts.issuer.example), mentre l'affermazione EVT iss è un'origine HTTPS completa (https://accounts.issuer.example, senza barra finale). Aggiungi il prefisso https:// al valore del record DNS prima del confronto.

La convalida del binding della chiave (KB-JWT) non riesce

  • Nonce non corrispondente o scaduto: assicurati che il valore nonce visualizzato in <input> corrisponda al nonce della sessione attiva sul server e non sia stato sovrascritto da un'altra scheda o utilizzato da una richiesta precedente.
  • Mancata corrispondenza del pubblico (aud): l'attestazione aud è l'origine HTTPS del verificatore (https://verifier.example, senza percorso o barra finale).

Il confronto tra le attestazioni email (email) non riesce

  • Distinzione tra maiuscole e minuscole e canonizzazione: Chrome 156+ restituisce l'attestazione email byte per byte come inserita nel modulo, ma le versioni precedenti del browser o i provider potrebbero restituire un indirizzo canonico (ad esempio, First.Last@example.com per first.last@example.com). Utilizza un confronto senza distinzione tra maiuscole e minuscole quando metti in corrispondenza l'attestazione email del token con il valore del modulo inviato.