Para implementar la verificación por correo electrónico en tu sitio, actualiza el lenguaje de marcado de tu formulario para solicitar el token y agrega la validación del servidor para los tokens entrantes.
Regístrate en la prueba de origen
Los sitios que se verifican deben tener configurada la prueba de origen en su sitio.
A partir de Chrome 154, se admiten las pruebas de origen de terceros con una advertencia importante: el origen registrado para la prueba debe ser del mismo sitio que el emisor. Por ejemplo:
- Dominio de la entidad emisora:
issuer.example - Registrante de OT:
https://issuer.example - Origen de JavaScript:
https://issuer.example(ohttps://app.issuer.examplecon coincidencia de subdominio)
Configura los campos del formulario
Agrega un campo de token oculto a tu formulario de envío de correo electrónico:
<input
type="email"
name="email-address"
autocomplete="email">
<input
type="hidden"
name="token"
autocomplete="email-verification-token"
nonce="rAnD0m-VaLuE">
Requisitos de los campos:
- Campo de correo electrónico: Establece
type="email"yautocomplete="email"para que Chrome pueda autocompletar y reconocer la dirección. - Atributos del campo de token:
- Conjunto
autocomplete="email-verification-token": Chrome identifica este campo para completar el token cuando se envía. - Establece
nonce="<VALUE>": El sitio debe proporcionar un nonce único vinculado a la sesión para verificar el envío del formulario.
- Conjunto
Valida el token de verificación por correo electrónico (EVT)
Cuando el usuario envía el formulario, tu servidor recibe la dirección de correo electrónico y el token del campo oculto. Un campo de token vacío indica que el navegador o el proveedor no admiten la EVP, o que el usuario omitió la verificación. Si esto ocurre, vuelve a tu proceso de verificación existente, como enviar una OTP o un vínculo mágico.
Si hay un token, valídalo de la siguiente manera:
- Analiza el token con una biblioteca de SD-JWT.
- Valida los valores esperados y los reclamos de sesión.
- Verifica la delegación de DNS.
- Descubre los metadatos del emisor y recupera los JWKS.
- Verifica las firmas criptográficas y la vinculación de claves.
1. Analiza el token
El token usa el formato RFC 9901: JWT de divulgación selectiva (SD-JWT+KB). Usa las bibliotecas adecuadas para tu plataforma para analizar y validar el token.
Por ejemplo, para Node, puedes usar @sd-jwt/core y jose. En su forma sin procesar, se ve de la siguiente manera: un JWT firmado por la entidad emisora, seguido de cero o más divulgaciones y que termina con un JWT de vinculación de clave, con cada componente separado por una virgulilla:
<Issuer-signed EVT>~<Disclosure 1>~...~<Disclosure N>~<Key Binding JWT>
En la implementación actual, el token no contiene divulgaciones (<Issuer-signed EVT>~<Key Binding JWT>). Sin embargo, esto podría cambiar en el futuro.
Decodifica el token con la 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;
Si verifier.example verifica demo@provider.example, el token decodificado se verá similar al siguiente:
{
"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. Valida los valores esperados y los reclamos de sesión
Verifica que los valores básicos de la carga útil coincidan con los valores proporcionados y esperados:
email_verified: Debe sertrue.email: Debe coincidir con la dirección de correo electrónico enviada en el formulario.aud(público): Debe coincidir con el origen de tu sitio.nonce: Debe coincidir con el nonce proporcionado en tu formulario.iat(fecha de emisión) yexp(fecha de vencimiento): Confirma que el token se encuentre dentro de su período válido y que no haya vencido.
3. Verifica la delegación de DNS
Verifica el registro DNS _email-verification del dominio de la dirección de correo electrónico. Por ejemplo, para demo@gmail.com, consulta el registro TXT de _email-verification.gmail.com. En el caso de este proveedor, la consulta devuelve la ubicación del proveedor de la cuenta, es decir, accounts.google.com.
$ dig +short TXT _email-verification.gmail.com
"iss=accounts.google.com"
Verifica que el esquema de la entidad emisora sea https:// y que https://<domain> coincida con el reclamo iss en el EVT.
4. Verifica la firma del EVT
Recupera los metadatos de descubrimiento del emisor 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"]
}
Recupera el conjunto de claves web JSON de jwks_uri.
Usa tu biblioteca de SD-JWT para verificar el paquete de tokens. La biblioteca coordina la validación:
- Validar la firma del emisor en el EVT con respecto al JWKS recuperado
- Validar la firma del navegador en el KB-JWT con la clave pública efímera en
cnf.jwk - Verifica la vinculación de la clave (
aud,noncey el hash del resumensd_hash).
Ejemplo de lógica de verificación en 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;
Si todos los pasos se completan correctamente, habrás verificado la dirección de correo electrónico con el proveedor. Si falla la verificación, vuelve a enviar un correo electrónico de confirmación al usuario con tu flujo normal.
Solución de problemas
Si la verificación falla o el navegador no proporciona un token, verifica los siguientes problemas comunes:
El campo de token está vacío al enviar
- Registro de la prueba de origen: Confirma que el encabezado
Origin-Trialo la etiqueta<meta>se publiquen en la página. En el caso de las pruebas de origen de terceros (Chrome 154 y versiones posteriores), el origen de la prueba registrado debe ser del mismo sitio que el emisor (https://<issuer-domain>). Puedes inspeccionar la configuración de la prueba de origen en un sitio en Herramientas para desarrolladores en Aplicación > Marcos > (selecciona el marco pertinente) > Pruebas de origen. - Marcado del formulario: Tanto
<input type="email" autocomplete="email">como<input type="hidden" autocomplete="email-verification-token" nonce="...">deben estar en el mismo elemento<form>(no aislados a través de los límites del DOM de sombra), ynonceno debe estar vacío. - Envío anticipado o página reutilizada: El navegador recupera el token en segundo plano después de que se ingresa o autocompleta el correo electrónico. Si envías el formulario antes de que se complete la solicitud, el token quedará vacío. Esto puede suceder si el usuario presiona Retorno para enviar el formulario después de ingresar su correo electrónico.
- Requisitos previos del navegador y el proveedor: El usuario debe haber accedido a un proveedor participante en el mismo perfil del navegador y tener habilitado el correo electrónico verificado en la configuración de Chrome (
chrome://settings/contactInfo).
Falla la verificación de la firma de la entidad emisora
- Falta el encabezado
kid: La reclamaciónkid(ID de clave) en el encabezado EVT y el JWKS es opcional (por ejemplo, Gmail omitekid). Si faltakid, itera por todas las claves candidatas en eljwks_uridel emisor en lugar de fallar en una búsqueda de ID de clave. - Identificadores de algoritmo (EdDSA y Ed25519): Las entidades emisoras y las bibliotecas pueden especificar
EdDSAoEd25519(junto conES256). Asegúrate de que la lógica de importación y verificación de JWK acepte ambos identificadores. - Formato de origen del emisor (
iss): El registro TXT de DNS (_email-verification.<domain>) contiene un nombre de host simple (iss=accounts.issuer.example), mientras que el reclamo de EVTisses un origen HTTPS completo (https://accounts.issuer.example, sin barra final). Agrega el prefijohttps://al valor del registro DNS antes de compararlo.
Falla la validación de la vinculación de claves (KB-JWT)
- Nonce no coincidente o vencido: Asegúrate de que el
noncerenderizado en el<input>coincida con el nonce de sesión activo en tu servidor y que no se haya reemplazado con otra pestaña ni se haya consumido con una solicitud anterior. - Incongruencia de público (
aud): El reclamoaudes el origen HTTPS del verificador (https://verifier.example, sin ruta de acceso ni barra final).
Falla la comparación de la reclamación de correo electrónico (email)
- Uso de mayúsculas y minúsculas y canonización: Chrome 156 y versiones posteriores devuelven el reclamo
emailbyte por byte tal como se ingresó en el formulario, pero las versiones anteriores del navegador o los proveedores pueden devolver una dirección canonizada (por ejemplo,First.Last@example.comparafirst.last@example.com). Usa una comparación que no distinga mayúsculas de minúsculas cuando compares el reclamoemaildel token con el valor del formulario enviado.