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(ouhttps://app.issuer.examplecom 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"eautocomplete="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.
- Defina
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:
- Analise o token usando uma biblioteca SD-JWT.
- Valide os valores esperados e as declarações de sessão.
- Verifique a delegação de DNS.
- Descobrir metadados do emissor e buscar JWKS.
- 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 sertrue.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) eexp(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:
- Validar a assinatura do emissor no EVT em relação ao JWKS buscado.
- Validar a assinatura do navegador no KB-JWT usando a chave pública efêmera em
cnf.jwk. - Verificar a vinculação de chave (
aud,noncee o hash de resumosd_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-Trialou 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), enoncenã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
kidausente: a declaraçãokid(ID da chave) no cabeçalho EVT e no JWKS é opcional. Por exemplo, o Gmail omitekid. Sekidestiver ausente, itere por todas as chaves candidatas nojwks_urido 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
EdDSAouEd25519(junto comES256). 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çãoissdo EVT é uma origem HTTPS completa (https://accounts.issuer.example, sem barra invertida no final). Adicione o prefixohttps://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
noncerenderizado 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çãoaudé 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
emailbyte 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.comparafirst.last@example.com). Use uma comparação sem diferenciação de maiúsculas e minúsculas ao comparar a declaraçãoemaildo token com o valor do formulário enviado.