Để triển khai quy trình xác minh email trên trang web của bạn, hãy cập nhật mã đánh dấu biểu mẫu để yêu cầu mã thông báo và thêm quy trình xác thực phía máy chủ cho các mã thông báo đến.
Đăng ký bản dùng thử theo nguyên gốc
Các trang web xác minh phải có thử nghiệm nguồn được định cấu hình trên trang web của họ.
Kể từ Chrome 154, các bản dùng thử theo nguyên gốc của bên thứ ba được hỗ trợ với một lưu ý quan trọng: nguồn gốc đã đăng ký cho bản dùng thử phải cùng trang web với tổ chức phát hành. Ví dụ:
- Miền của tổ chức phát hành:
issuer.example - Người đăng ký OT:
https://issuer.example - Nguồn gốc JavaScript:
https://issuer.example(hoặchttps://app.issuer.examplecó so khớp miền con)
Định cấu hình các trường trong biểu mẫu
Thêm một trường mã thông báo ẩn vào biểu mẫu gửi email:
<input
type="email"
name="email-address"
autocomplete="email">
<input
type="hidden"
name="token"
autocomplete="email-verification-token"
nonce="rAnD0m-VaLuE">
Yêu cầu về trường:
- Trường email: Đặt
type="email"vàautocomplete="email"để Chrome có thể tự động điền và nhận dạng địa chỉ. - Thuộc tính trường mã thông báo:
- Đặt
autocomplete="email-verification-token": Chrome xác định trường này để điền mã thông báo khi gửi. - Đặt
nonce="<VALUE>": Trang web phải cung cấp một số chỉ dùng một lần duy nhất theo phiên để xác minh việc gửi biểu mẫu.
- Đặt
Xác thực mã thông báo xác minh email (EVT)
Khi người dùng gửi biểu mẫu, máy chủ của bạn sẽ nhận được địa chỉ email và mã thông báo từ trường ẩn. Trường mã thông báo trống cho biết trình duyệt hoặc nhà cung cấp không hỗ trợ EVP hoặc người dùng đã bỏ qua quy trình xác minh. Nếu điều này xảy ra, hãy quay lại quy trình xác minh hiện tại của bạn, chẳng hạn như gửi OTP hoặc đường liên kết truy cập một lần.
Nếu có mã thông báo, hãy xác thực mã thông báo đó như sau:
- Phân tích cú pháp mã thông báo bằng thư viện SD-JWT.
- Xác thực các giá trị dự kiến và các yêu cầu về phiên.
- Xác minh việc uỷ quyền DNS.
- Khám phá siêu dữ liệu của tổ chức phát hành và tìm nạp JWKS.
- Xác minh chữ ký mật mã và liên kết khoá.
1. Phân tích cú pháp mã thông báo
Mã thông báo này sử dụng định dạng RFC 9901: JWT công bố có chọn lọc (SD-JWT+KB). Sử dụng các thư viện phù hợp cho nền tảng của bạn để phân tích cú pháp và xác thực mã thông báo.
Ví dụ: đối với Node, bạn có thể sử dụng @sd-jwt/core và jose. Ở dạng thô, dữ liệu này trông như sau: một JWT do Tổ chức phát hành ký, theo sau là từ 0 đến nhiều Thông tin công bố và kết thúc bằng một JWT Liên kết khoá, trong đó mỗi thành phần được phân tách bằng dấu ngã:
<Issuer-signed EVT>~<Disclosure 1>~...~<Disclosure N>~<Key Binding JWT>
Trong quá trình triển khai hiện tại, mã thông báo không chứa thông tin công bố nào (<Issuer-signed EVT>~<Key Binding JWT>). Tuy nhiên, điều này có thể thay đổi trong tương lai.
Giải mã mã thông báo bằng thư viện:
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;
Nếu verifier.example xác minh demo@provider.example, mã thông báo đã giải mã sẽ có dạng tương tự như sau:
{
"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. Xác thực các giá trị dự kiến và yêu cầu về phiên
Kiểm tra để đảm bảo các giá trị cơ bản trong tải trọng khớp với các giá trị bạn cung cấp và dự kiến:
email_verified: Phải làtrue.email: Phải khớp với địa chỉ email được gửi trong biểu mẫu.aud(đối tượng): Phải khớp với nguồn gốc của trang web.nonce: Phải khớp với số chỉ dùng một lần được cung cấp trong biểu mẫu của bạn.iat(phát hành lúc) vàexp(hết hạn): Xác nhận rằng mã thông báo nằm trong khung thời gian hợp lệ và chưa hết hạn.
3. Xác minh việc uỷ quyền DNS
Xác minh bản ghi DNS _email-verification cho miền của địa chỉ email. Ví dụ: đối với demo@gmail.com, hãy truy vấn bản ghi TXT _email-verification.gmail.com. Đối với nhà cung cấp này, truy vấn sẽ trả về vị trí của nhà cung cấp tài khoản, tức là accounts.google.com.
$ dig +short TXT _email-verification.gmail.com
"iss=accounts.google.com"
Xác minh rằng lược đồ của tổ chức phát hành là https:// và https://<domain> khớp với yêu cầu iss trong EVT.
4. Xác minh chữ ký EVT
Tìm nạp siêu dữ liệu khám phá của tổ chức phát hành từ 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"]
}
Tìm nạp JSON Web Key Set từ jwks_uri.
Sử dụng thư viện SD-JWT để xác minh gói mã thông báo. Thư viện này điều phối quá trình xác thực:
- Xác thực chữ ký của tổ chức phát hành trên EVT dựa trên JWKS đã tìm nạp.
- Xác thực chữ ký của trình duyệt trên KB-JWT bằng khoá công khai tạm thời trong
cnf.jwk. - Xác minh mối liên kết khoá (
aud,noncevà hàm băm của bản tóm tắtsd_hash).
Ví dụ về logic xác minh trong 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;
Nếu tất cả các bước đều thành công, tức là bạn đã xác minh địa chỉ email với nhà cung cấp. Nếu xác minh không thành công, hãy quay lại việc gửi email xác nhận cho người dùng bằng quy trình thông thường của bạn.
Khắc phục sự cố
Nếu quá trình xác minh không thành công hoặc trình duyệt không cung cấp mã thông báo, hãy kiểm tra các vấn đề thường gặp sau:
Trường mã thông báo bị trống khi gửi
- Đăng ký bản dùng thử theo nguyên gốc: Xác nhận rằng tiêu đề
Origin-Trialhoặc thẻ<meta>được phân phát trên trang. Đối với bản dùng thử theo nguyên gốc của bên thứ ba (Chrome 154 trở lên), nguyên gốc dùng thử đã đăng ký phải cùng trang web với đơn vị phát hành (https://<issuer-domain>). Bạn có thể kiểm tra cấu hình bản dùng thử theo nguyên gốc trên một trang web trong Công cụ cho nhà phát triển trong phần Ứng dụng > Khung > (chọn khung có liên quan) > Bản dùng thử theo nguyên gốc. - Đánh dấu biểu mẫu: Cả
<input type="email" autocomplete="email">và<input type="hidden" autocomplete="email-verification-token" nonce="...">đều phải nằm trong cùng một phần tử<form>(không được tách biệt trên các ranh giới Shadow DOM) vànoncekhông được để trống. - Gửi sớm hoặc sử dụng lại trang: Trình duyệt tìm nạp mã thông báo ở chế độ nền sau khi người dùng nhập hoặc tự động điền email. Việc gửi trước khi yêu cầu hoàn tất sẽ khiến mã thông báo bị trống. Điều này có thể xảy ra nếu người dùng nhấn phím Return để gửi biểu mẫu sau khi nhập email.
- Điều kiện tiên quyết về trình duyệt và nhà cung cấp: Người dùng phải đăng nhập vào một nhà cung cấp tham gia trong cùng một hồ sơ trình duyệt và đã bật Email đã xác minh trong phần cài đặt Chrome (
chrome://settings/contactInfo).
Không xác minh được chữ ký của tổ chức phát hành
- Thiếu tiêu đề
kid: Thông báo xác nhận quyền sở hữukid(Mã khoá) trong tiêu đề EVT và JWKS là không bắt buộc (ví dụ: Gmail bỏ quakid). Nếukidkhông có, hãy lặp lại tất cả các khoá đề xuất trongjwks_uricủa tổ chức phát hành thay vì thất bại khi tra cứu mã khoá. - Giá trị nhận dạng thuật toán (EdDSA và Ed25519): Các tổ chức phát hành và thư viện có thể chỉ định
EdDSAhoặcEd25519(cùng vớiES256). Đảm bảo logic xác minh và nhập JWK của bạn chấp nhận cả hai giá trị nhận dạng. - Định dạng nguồn của tổ chức phát hành (
iss): Bản ghi DNS TXT (_email-verification.<domain>) chứa một tên máy chủ trần (iss=accounts.issuer.example), trong khi yêu cầu EVTisslà một nguồn HTTPS đầy đủ (https://accounts.issuer.example, không có dấu gạch chéo ở cuối). Thêm tiền tốhttps://vào giá trị bản ghi DNS trước khi so sánh.
Không xác thực được hoạt động liên kết khoá (KB-JWT)
- Số chỉ dùng một lần không khớp hoặc đã hết hạn: Đảm bảo
nonceđược kết xuất trong<input>khớp với số chỉ dùng một lần của phiên đang hoạt động trên máy chủ của bạn và chưa bị một thẻ khác ghi đè hoặc bị một yêu cầu trước đó sử dụng. - Đối tượng (
aud) không khớp: Xác nhận quyền sở hữuaudlà nguồn HTTPS của trình xác minh (https://verifier.example, không có đường dẫn hoặc dấu gạch chéo ở cuối).
Không so sánh được yêu cầu xác nhận quyền sở hữu qua email (email)
- Phân biệt chữ hoa chữ thường và chuẩn hoá: Chrome 156 trở lên trả về yêu cầu
emailtừng byte như đã nhập trong biểu mẫu, nhưng các phiên bản trình duyệt hoặc nhà cung cấp trước đó có thể trả về một địa chỉ được chuẩn hoá (ví dụ:First.Last@example.comchofirst.last@example.com). Sử dụng so sánh không phân biệt chữ hoa chữ thường khi so khớp yêu cầuemailcủa mã thông báo với giá trị biểu mẫu đã gửi.