Implementação da entidade confiável

Para implementar a verificação de e-mail no seu site, atualize a marcação do formulário para solicitar o token e adicione a validação do lado do servidor para os tokens recebidos.

Inscrever-se no teste de origem

Os sites que precisam ser verificados precisam ter o teste de origem configurado.

A partir do Chrome 154, os testes de origem de terceiros são compatíveis com uma observação importante: a origem registrada para o teste precisa ser do mesmo site que o emissor. Exemplo:

  • Domínio do emissor: issuer.example
  • Registrante de OT: https://issuer.example
  • Origem do JavaScript: https://issuer.example (ou https://app.issuer.example com correspondência de subdomínio)

Configurar campos de formulário

Adicione um campo de token oculto ao formulário de envio de e-mail:

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

Requisitos de campo:

  • Campo de e-mail: defina type="email" e autocomplete="email" para que o Chrome possa preencher automaticamente e reconhecer o endereço.
  • Atributos do campo de token:
    • Defina autocomplete="email-verification-token": o Chrome identifica esse campo para preencher o token no envio.
    • Defina nonce="<VALUE>": o site precisa fornecer um nonce exclusivo vinculado à sessão para verificar o envio do formulário.

Validar o token de verificação de e-mail (EVT)

Quando o usuário envia o formulário, seu servidor recebe o endereço de e-mail e o token do campo oculto. Um campo de token vazio indica que o navegador ou provedor não oferece suporte à EVP ou que o usuário pulou a verificação. Se isso acontecer, volte ao processo de verificação atual, como o envio de uma senha única ou um link mágico.

Se um token estiver presente, valide-o da seguinte forma:

  1. Analise o token usando uma biblioteca SD-JWT.
  2. Valide os valores esperados e as declarações de sessão.
  3. Verifique a delegação de DNS.
  4. Descobrir metadados do emissor e buscar JWKS.
  5. Verificar assinaturas criptográficas e vinculação de chaves.

1. Analisar o token

O token usa o formato RFC 9901: Selective Disclosure JWT (SD-JWT+KB). Use as bibliotecas adequadas para sua plataforma para analisar e validar o token. Por exemplo, para Node, use @sd-jwt/core e jose. Na forma bruta, isso se parece com o seguinte: um JWT assinado pelo emissor, seguido por zero ou mais divulgações e terminado com um JWT de vinculação de chave com cada componente separado por um til:

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

Na implementação atual, o token não contém divulgações (<Issuer-signed EVT>~<Key Binding JWT>), mas isso pode mudar no futuro.

Decodifique o token com a biblioteca:

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 verificar demo@provider.example, o token decodificado será semelhante a este:

{
  "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. Validar valores esperados e declarações de sessão

Verifique se os valores básicos no payload correspondem aos valores fornecidos e esperados:

  • email_verified: precisa ser true.
  • email: precisa corresponder ao endereço de e-mail enviado no formulário.
  • aud (público-alvo): precisa corresponder à origem do seu site.
  • nonce: precisa corresponder ao nonce fornecido no formulário.
  • iat (emitido em) e exp (expiração): confirme se o token está dentro do período de validade e não expirou.

3. Verificar a delegação de DNS

Verifique o registro DNS _email-verification do domínio do endereço de e-mail. Por exemplo, para demo@gmail.com, consulte o registro TXT _email-verification.gmail.com. Para esse provedor, a consulta retorna o local do provedor da conta, ou seja, accounts.google.com.

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

Verifique se o esquema do emissor é https:// e se https://<domain> corresponde à declaração iss no EVT.

4. Verificar a assinatura do EVT

Extraia os metadados de descoberta do emissor 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"]
}

Extraia o conjunto de chaves da Web JSON de jwks_uri.

Use sua biblioteca SD-JWT para verificar o pacote de token. A biblioteca coordena a validação:

  1. Validar a assinatura do emissor no EVT em relação ao JWKS buscado.
  2. Validar a assinatura do navegador no KB-JWT usando a chave pública efêmera em cnf.jwk.
  3. Verificar a vinculação de chave (aud, nonce e o hash de resumo sd_hash).

Exemplo de lógica de verificação em 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 todas as etapas forem concluídas, você terá verificado o endereço de e-mail com o provedor. Se a verificação falhar, envie um e-mail de confirmação para o usuário usando seu fluxo normal.

Solução de problemas

Se a verificação falhar ou o navegador não fornecer um token, confira os problemas comuns a seguir:

O campo de token está vazio no envio

  • Registro do teste de origem: confirme se o cabeçalho Origin-Trial ou a tag <meta> está sendo veiculada na página. Para testes de origem de terceiros (Chrome 154 ou mais recente), a origem registrada precisa ser do mesmo site que o emissor (https://<issuer-domain>). Você pode inspecionar a configuração do teste de origem em um site nas DevTools em Application > Frames > (selecione o frame relevante) > Origin trials.
  • Marcação de formulário: <input type="email" autocomplete="email"> e <input type="hidden" autocomplete="email-verification-token" nonce="..."> precisam estar no mesmo elemento <form> (não isolados em limites do Shadow DOM), e nonce não pode estar vazio.
  • Envio antecipado ou página reutilizada: o navegador busca o token em segundo plano depois que o e-mail é inserido ou preenchido automaticamente. Enviar antes da conclusão da solicitação deixa o token vazio. Isso pode acontecer se o usuário pressionar "Return" para enviar o formulário depois de inserir o e-mail.
  • Pré-requisitos de navegador e provedor: o usuário precisa fazer login em um provedor participante no mesmo perfil do navegador e ter o e-mail verificado ativado nas configurações do Chrome (chrome://settings/contactInfo).

Falha na verificação da assinatura do emissor

  • Cabeçalho kid ausente: a declaração kid (ID da chave) no cabeçalho EVT e no JWKS é opcional. Por exemplo, o Gmail omite kid. Se kid estiver ausente, itere por todas as chaves candidatas no jwks_uri do emissor em vez de falhar em uma pesquisa de ID da chave.
  • Identificadores de algoritmo (EdDSA e Ed25519): os emissores e as bibliotecas podem especificar EdDSA ou Ed25519 (junto com ES256). Verifique se a lógica de importação e verificação de JWK aceita os dois identificadores.
  • Formato de origem do emissor (iss): o registro TXT do DNS (_email-verification.<domain>) contém um nome de host simples (iss=accounts.issuer.example), enquanto a declaração iss do EVT é uma origem HTTPS completa (https://accounts.issuer.example, sem barra invertida no final). Adicione o prefixo https:// ao valor do registro DNS antes de comparar.

A validação da vinculação de chaves (KB-JWT) falha

  • Nonce incompatível ou expirado: verifique se o nonce renderizado no <input> corresponde ao nonce da sessão ativa no seu servidor e não foi substituído por outra guia nem consumido por uma solicitação anterior.
  • Divergência de público-alvo (aud): a declaração aud é a origem HTTPS do verificador (https://verifier.example, sem caminho ou barra invertida à direita).

Falhas na comparação de declarações de e-mail (email)

  • Uso de maiúsculas e minúsculas e canonicalização: o Chrome 156 e versões mais recentes retornam a declaração email byte a byte conforme inserida no formulário, mas versões anteriores do navegador ou provedores podem retornar um endereço canonicalizado (por exemplo, First.Last@example.com para first.last@example.com). Use uma comparação sem diferenciação de maiúsculas e minúsculas ao comparar a declaração email do token com o valor do formulário enviado.