Untuk menerapkan verifikasi email di situs Anda, perbarui markup formulir untuk meminta token dan tambahkan validasi sisi server untuk token yang masuk.
Mendaftar untuk uji coba origin
Situs yang memverifikasi harus mengonfigurasi uji coba origin di situsnya.
- Mendaftar uji coba origin
- Menayangkan token uji coba origin
Mulai Chrome 154, uji coba origin pihak ketiga didukung dengan peringatan penting: origin yang terdaftar untuk uji coba harus memiliki situs yang sama dengan penerbit. Contoh:
- Domain penerbit:
issuer.example - Pendaftar OT:
https://issuer.example - Origin JavaScript:
https://issuer.example(atauhttps://app.issuer.exampledengan pencocokan subdomain)
Mengonfigurasi kolom formulir
Tambahkan kolom token tersembunyi ke formulir pengiriman email Anda:
<input
type="email"
name="email-address"
autocomplete="email">
<input
type="hidden"
name="token"
autocomplete="email-verification-token"
nonce="rAnD0m-VaLuE">
Persyaratan kolom:
- Kolom email: Tetapkan
type="email"danautocomplete="email"agar Chrome dapat mengisi otomatis dan mengenali alamat. - Atribut kolom token:
- Setel
autocomplete="email-verification-token": Chrome mengidentifikasi kolom ini untuk mengisi token saat pengiriman. - Set
nonce="<VALUE>": Situs harus memberikan nonce unik yang terikat sesi untuk memverifikasi pengiriman formulir.
- Setel
Memvalidasi token verifikasi email (EVT)
Saat pengguna mengirimkan formulir, server Anda akan menerima alamat email dan token dari kolom tersembunyi. Kolom token kosong menunjukkan bahwa browser atau penyedia tidak mendukung EVP, atau pengguna melewati verifikasi. Jika hal ini terjadi, kembali ke proses verifikasi yang ada, seperti mengirim OTP atau link ajaib.
Jika ada token, validasi token tersebut sebagai berikut:
- Parse token menggunakan library SD-JWT.
- Memvalidasi nilai yang diharapkan dan klaim sesi.
- Verifikasi delegasi DNS.
- Temukan metadata penerbit dan ambil JWKS.
- Verifikasi tanda tangan kriptografi dan pengikatan kunci.
1. Mengurai token
Token menggunakan format RFC 9901: Selective Disclosure JWT
(SD-JWT+KB). Gunakan library yang sesuai untuk platform Anda guna mengurai dan memvalidasi token.
Misalnya, untuk Node, Anda dapat menggunakan
@sd-jwt/core dan
jose. Dalam bentuk mentahnya, tampilannya seperti
ini: JWT yang ditandatangani Penerbit, diikuti dengan nol atau lebih Pengungkapan, dan diakhiri
dengan JWT Pengikatan Kunci dengan setiap komponen dipisahkan oleh tilde:
<Issuer-signed EVT>~<Disclosure 1>~...~<Disclosure N>~<Key Binding JWT>
Dalam implementasi saat ini, token berisi nol pengungkapan
(<Issuer-signed EVT>~<Key Binding JWT>). Namun, hal ini dapat berubah pada masa mendatang.
Dekode token dengan library:
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;
Jika verifier.example memverifikasi demo@provider.example, token yang didekode akan terlihat
mirip dengan berikut:
{
"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. Memvalidasi nilai yang diharapkan dan klaim sesi
Periksa apakah nilai dasar dalam payload cocok dengan nilai yang Anda berikan dan harapkan:
email_verified: Harustrue.email: Harus cocok dengan alamat email yang dikirimkan dalam formulir.aud(audiens): Harus cocok dengan asal situs Anda.nonce: Harus cocok dengan nonce yang diberikan dalam formulir Anda.iat(dikeluarkan pada) danexp(masa berlaku): Pastikan token berada dalam jangka waktu yang valid dan belum habis masa berlakunya.
3. Memverifikasi delegasi DNS
Verifikasi data DNS _email-verification untuk domain alamat email. Misalnya, untuk demo@gmail.com, kueri data TXT _email-verification.gmail.com. Untuk penyedia ini, kueri menampilkan lokasi penyedia akun, yaitu accounts.google.com.
$ dig +short TXT _email-verification.gmail.com
"iss=accounts.google.com"
Verifikasi bahwa skema penerbit adalah https:// dan https://<domain> cocok dengan klaim iss di EVT.
4. Memverifikasi tanda tangan EVT
Ambil metadata penemuan penerbit dari
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"]
}
Ambil JSON Web Key Set dari jwks_uri.
Gunakan library SD-JWT Anda untuk memverifikasi paket token. Library mengoordinasikan validasi:
- Memvalidasi tanda tangan penerbit pada EVT terhadap JWKS yang diambil.
- Memvalidasi tanda tangan browser pada KB-JWT menggunakan kunci publik sementara di
cnf.jwk. - Memverifikasi pengikatan kunci (
aud,nonce, dan hash ringkasansd_hash).
Contoh logika verifikasi di 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;
Jika semua langkah berhasil, Anda telah memverifikasi alamat email terhadap penyedia. Jika verifikasi gagal, kirim email konfirmasi kepada pengguna menggunakan alur normal Anda.
Pemecahan masalah
Jika verifikasi gagal atau browser tidak memberikan token, periksa masalah umum berikut:
Kolom token kosong saat pengiriman
- Pendaftaran uji coba origin: Pastikan header
Origin-Trialatau tag<meta>ditayangkan di halaman. Untuk uji coba origin pihak ketiga (Chrome 154+), origin uji coba terdaftar harus memiliki situs yang sama dengan penerbit (https://<issuer-domain>). Anda dapat memeriksa konfigurasi uji coba origin di situs di DevTools pada bagian Application > Frames > (pilih frame yang relevan) > Origin trials. - Markup formulir:
<input type="email" autocomplete="email">dan<input type="hidden" autocomplete="email-verification-token" nonce="...">harus berada dalam elemen<form>yang sama (tidak terisolasi di seluruh batas Shadow DOM), dannoncetidak boleh kosong. - Pengiriman awal atau halaman yang digunakan kembali: Browser mengambil token di latar belakang setelah email dimasukkan atau diisi otomatis. Mengirimkan sebelum permintaan selesai akan membuat token kosong. Hal ini dapat terjadi jika pengguna menekan Kembali untuk mengirimkan formulir setelah memasukkan emailnya.
- Prasyarat browser dan penyedia: Pengguna harus login ke penyedia yang berpartisipasi di profil browser yang sama dan mengaktifkan Email Terverifikasi di setelan Chrome (
chrome://settings/contactInfo).
Verifikasi tanda tangan penerbit gagal
- Header
kidtidak ada: Klaimkid(ID Kunci) di header EVT dan JWKS bersifat opsional (misalnya, Gmail tidak menyertakankid). Jikakidtidak ada, lakukan iterasi pada semua kunci kandidat dijwks_uripenerbit, bukan gagal pada pencarian ID kunci. - ID algoritma (EdDSA dan Ed25519): Penerbit dan library dapat menentukan
EdDSAatauEd25519(bersama denganES256). Pastikan logika verifikasi dan impor JWK Anda menerima kedua ID tersebut. - Format asal Penerbit (
iss): Data TXT DNS (_email-verification.<domain>) berisi nama host kosong (iss=accounts.issuer.example), sedangkan klaimissEVT adalah asal HTTPS lengkap (https://accounts.issuer.example, tanpa garis miring di akhir). Tambahkan awalanhttps://ke nilai data DNS sebelum membandingkan.
Validasi pengikatan kunci (KB-JWT) gagal
- Nonce tidak cocok atau telah habis masa berlakunya: Pastikan
nonceyang dirender di<input>cocok dengan nonce sesi aktif di server Anda dan belum ditimpa oleh tab lain atau digunakan oleh permintaan sebelumnya. - Ketidakcocokan audiens (
aud): Klaimaudadalah asal HTTPS verifier (https://verifier.example, tanpa jalur atau garis miring di akhir).
Perbandingan klaim email (email) gagal
- Penggunaan huruf besar/kecil dan kanonikalisasi: Chrome 156+ menampilkan klaim
emailbyte demi byte seperti yang dimasukkan dalam formulir, tetapi versi atau provider browser yang lebih lama dapat menampilkan alamat yang dikanonikalisasi (misalnya,First.Last@example.comuntukfirst.last@example.com). Gunakan perbandingan yang tidak peka huruf besar/kecil saat mencocokkan klaimemailtoken dengan nilai formulir yang dikirimkan.