Para mais detalhes, confira o código de demonstração do provedor de e-mail simulado e consulte as etapas do emissor nas propostas da API Email Verification e do protocolo Email Verification.
Como emissor, você não precisa se inscrever no teste de origem nem fornecer um token porque o site da parte confiante aciona o comportamento do navegador. Verifique se os endpoints estão configurados para responder a essas solicitações.
Configurar a descoberta de emissores
Para permitir que os navegadores descubram automaticamente seus endpoints de verificação quando um
endereço de e-mail pertencente ao seu domínio for selecionado, exponha sua configuração
usando DNS e um endpoint HTTP .well-known.
Configurar um registro de delegação de DNS
Configure um registro TXT de DNS no seu domínio de e-mail que delegue a autoridade de verificação ao identificador do emissor. Esses identificadores podem usar o mesmo domínio, dependendo da sua infraestrutura.
Formato do registro: _email-verification.<email-domain>
Exemplo de arquivo de zona:
_email-verification.example.com IN TXT "iss=accounts.issuer.example"
Hospedar um endpoint .well-known/email-verification
Hospede um arquivo JSON de metadados no domínio do emissor no caminho /.well-known/.
Esse arquivo descreve suas capacidades de emissão e os algoritmos de assinatura
criptográfica compatíveis com sua infraestrutura.
Endpoint: https://<issuer-domain>/.well-known/email-verification
Exemplo de resposta:
{
"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"]
}
Hospedar um endpoint .well-known/web-identity
Talvez você já tenha implementado um recurso JSON .well-known adicional como parte da API Federated Credentials (FedCM). Esse recurso fornece links para o endpoint de contas e o URL de login.
Endpoint: https://<domain>/.well-known/web-identity
Exemplo de resposta:
{
"accounts_endpoint": "https://accounts.issuer.example/accounts",
"login_url": "https://accounts.issuer.example/login"
}
Usar um endpoint de contas
O endpoint "accounts" da API FedCM fornece uma lista de contas conectadas no momento. O exemplo a seguir mostra uma resposta mínima. Para mais detalhes, consulte o guia de implementação do provedor de identidade.
Endpoint: conforme especificado em .well-known/web-identity
Veja a seguir um exemplo de resposta:
{
"accounts": [
{
"id": "demo-example",
"name": "Demo User",
"email": "demo@example.com",
"given_name": "Demo"
}
]
}
Integrar com a API Login Status
O usuário precisa ter uma sessão ativa com o provedor, e você precisa sinalizar isso para o navegador usando a API Login Status (em inglês).
Quando um usuário fizer login ou sair, veicule o cabeçalho de resposta HTTP correspondente:
Set-Login: logged-in
Set-Login: logged-out
Como alternativa, atualize o status usando JavaScript no contexto do aplicativo da Web:
navigator.login.setStatus("logged-in");
navigator.login.setStatus("logged-out");
Processar solicitações de emissão
Seu issuance_endpoint recebe uma solicitação application/json POST que
contém a chave email e os cabeçalhos Assinaturas de mensagens
HTTP para Signature,
Signature-Input e Signature-Key.
Use uma biblioteca que ofereça suporte a cabeçalhos estruturados e assinaturas de mensagens HTTP para
seu ambiente. No Node.js, é possível usar
structured-headers e
http-message-sig.
Formato completo da solicitação:
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"}
Analise e valide a solicitação:
- Autenticação de sessão: valide os cookies de sessão próprios enviados com a solicitação. O usuário precisa ser autenticado.
- Cabeçalho
Sec-Fetch-Dest: defina comoemail-verification. - Assinaturas de mensagens HTTP: verifique a assinatura da solicitação usando a chave pública efêmera em
Signature-Keye valide oContent-Digest. - Payload: o corpo JSON contém a string
emailsolicitada para verificação.
Resposta de emissão
Após a validação bem-sucedida da sessão e do token de solicitação, gere um JWT de divulgação seletiva (SD-JWT) assinado retornado como JSON usando as bibliotecas adequadas para sua plataforma. Por exemplo, para Node, use
@sd-jwt/core e
jose.
O formato de payload bruto será semelhante a este:
{
"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
}
Crie, assine e retorne o token:
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);
O corpo da resposta resultante é semelhante a:
{
"issuance_token": "tOkEn123tOkEn123tOkEn123...~"
}
Depois de criar e assinar o token de verificação de e-mail, o navegador passa isso para o site do verificador para validação.
Solução de problemas
Se o navegador não entrar em contato com seus endpoints ou rejeitar os tokens emitidos, verifique os seguintes problemas comuns:
O navegador nunca chama accounts_endpoint ou issuance_endpoint
- Status de login não definido: o Chrome só consulta seus endpoints se souber que o usuário fez login. Confira se o fluxo de login define o cabeçalho HTTP
Set-Login: logged-inou chamanavigator.login.setStatus("logged-in"). - Cookies de sessão bloqueados (
SameSite=None): o navegador busca seuaccounts_endpointeissuance_endpointde um terceiro confiável em um site diferente. Como essas são solicitações entre sites, seu cookie de sessão precisa incluirSameSite=None; Secure. Um cookieSameSite=Laxfunciona ao testar em um verificador do mesmo site, mas é omitido em solicitações entre sites. - Descoberta ou incompatibilidade de conta: verifique se
_email-verification.<email-domain>retorna um único registro TXT (iss=<issuer-domain>, semhttps://), se os dois endpoints.well-knownretornamContent-Type: application/jsone se a respostaaccounts_endpointinclui uma conta cujoemailcorresponde ao endereço inserido.
A validação da solicitação de emissão falha
- Ortografia do cabeçalho
Sec-Fetch-Dest: o Chrome 154 e versões mais recentes enviamSec-Fetch-Dest: email-verification(com um hífen), enquanto o Chrome 153 enviavaemailverification(sem um hífen). Aceite os dois valores durante o lançamento. - Incompatibilidade de assinatura de mensagem HTTP (
@authority) por trás de um proxy: ao verificar a assinatura RFC 9421, o componente@authorityreflete o host público. Se o servidor estiver atrás de um proxy reverso ou balanceador de carga, reconstrua o URL de verificação usandoX-Forwarded-Host(ou sua origem pública) em vez do nome do host interno e calculeContent-Digestnos bytes do corpo da solicitação bruta antes da análise JSON.
O navegador rejeita o issuance_token retornado
- Declaração
emailmodificada ou canonizada: a partir do Chrome 156, o navegador verifica se a declaração EVTemailcorresponde aoemailsolicitado byte a byte. Se o back-end normalizar o endereço para um formato de conta canônica (como retornarFirst.Last@example.comquandofirst.LAST@example.comfoi solicitado), o Chrome vai descartar o token. Corresponda a solicitação à conta do usuário, mas retorne a stringemailexata recebida no corpo da solicitação. - Til final ausente (
~): mesmo sem divulgações, oissuance_tokenprecisa ser um SD-JWT válido que termine com um til (<Issuer-signed-JWT>~) para que o navegador possa anexar o<KB-JWT>. issoucnf.jwkincompatíveis: verifique se a declaraçãoissdo EVT é a origem HTTPS exata (https://<issuer-domain>, sem barra invertida à direita) que corresponde aos metadados.well-known/email-verification, e secnf.jwkincorpora a chave pública efêmera do navegador do cabeçalhoSignature-Key.