ดูรายละเอียดเพิ่มเติมได้โดยดูโค้ดการสาธิตผู้ให้บริการอีเมลจำลอง และดูขั้นตอนของผู้ให้บริการในข้อเสนอAPI การยืนยันอีเมลและโปรโตคอลการยืนยันอีเมล
ในฐานะผู้ออก คุณไม่จำเป็นต้องลงชื่อสมัครใช้ Origin Trial หรือระบุโทเค็น เนื่องจากเว็บไซต์ Relying Party จะทริกเกอร์ลักษณะการทำงานของเบราว์เซอร์ ตรวจสอบว่าได้กำหนดค่า ปลายทางให้ตอบกลับคำขอเหล่านั้นแล้ว
กำหนดค่าการค้นหาผู้ออกบัตร
หากต้องการอนุญาตให้เบราว์เซอร์ค้นหาปลายทางการยืนยันโดยอัตโนมัติเมื่อมีการเลือก
อีเมลที่เป็นของโดเมน ให้เปิดเผยการกำหนดค่าโดยใช้ DNS และ.well-known ปลายทาง HTTP
กำหนดค่าระเบียนผู้มอบสิทธิ์ DNS
กำหนดค่าระเบียน TXT ของ DNS ในโดเมนอีเมลที่มอบสิทธิ์การยืนยัน ให้กับตัวระบุผู้ออก ตัวระบุเหล่านี้สามารถใช้โดเมนเดียวกันได้ ขึ้นอยู่กับโครงสร้างพื้นฐานของคุณ
รูปแบบการบันทึก: _email-verification.<email-domain>
ตัวอย่างไฟล์โซน
_email-verification.example.com IN TXT "iss=accounts.issuer.example"
โฮสต์.well-known/email-verificationปลายทาง
โฮสต์ไฟล์ข้อมูลเมตา JSON ในโดเมนผู้ออกบัตรภายใต้เส้นทาง /.well-known/
ไฟล์นี้ระบุความสามารถในการออกบัตรและอัลกอริทึมการลงนามด้วยการเข้ารหัส
ที่โครงสร้างพื้นฐานของคุณรองรับ
ปลายทาง: https://<issuer-domain>/.well-known/email-verification
ตัวอย่างคำตอบ
{
"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"]
}
โฮสต์.well-known/web-identityปลายทาง
คุณอาจได้ติดตั้งใช้งานทรัพยากร JSON เพิ่มเติมของ .well-known เป็นส่วนหนึ่งของ Federated Credentials (FedCM)
API แล้ว แหล่งข้อมูลนี้มี
ลิงก์ไปยังปลายทางของบัญชีและ URL การเข้าสู่ระบบ
ปลายทาง: https://<domain>/.well-known/web-identity
ตัวอย่างคำตอบ
{
"accounts_endpoint": "https://accounts.issuer.example/accounts",
"login_url": "https://accounts.issuer.example/login"
}
ใช้ปลายทางบัญชี
ปลายทางบัญชีจาก FedCM API จะแสดงรายการบัญชีที่ลงชื่อเข้าใช้ ในขณะนั้น ตัวอย่างต่อไปนี้แสดงการตอบกลับขั้นต่ำ ดูรายละเอียดเพิ่มเติมได้ที่คู่มือการติดตั้งใช้งานผู้ให้บริการข้อมูลประจำตัว
ปลายทาง: ตามที่ระบุไว้ใน .well-known/web-identity
ตัวอย่างการตอบกลับมีดังนี้
{
"accounts": [
{
"id": "demo-example",
"name": "Demo User",
"email": "demo@example.com",
"given_name": "Demo"
}
]
}
ผสานรวมกับ Login Status API
ผู้ใช้ต้องมีเซสชันที่ใช้งานอยู่กับผู้ให้บริการ และคุณต้องส่งสัญญาณนั้น ไปยังเบราว์เซอร์โดยใช้ Login Status API
เมื่อผู้ใช้ลงชื่อเข้าใช้หรือออกจากระบบสำเร็จ ให้แสดงส่วนหัวการตอบกลับ HTTP ที่ตรงกัน
Set-Login: logged-in
Set-Login: logged-out
หรือจะอัปเดตสถานะโดยใช้ JavaScript ในบริบทของเว็บแอปพลิเคชันก็ได้
navigator.login.setStatus("logged-in");
navigator.login.setStatus("logged-out");
จัดการคำขอออกบัตร
issuance_endpoint ได้รับคำขอ application/json POST ที่มีคีย์ email และส่วนหัวลายเซ็นข้อความ HTTP สำหรับ Signature, Signature-Input และ Signature-Key
ใช้ไลบรารีที่รองรับส่วนหัวที่มีโครงสร้างและลายเซ็นข้อความ HTTP สำหรับสภาพแวดล้อมของคุณ ใน Node.js คุณสามารถใช้
structured-headers และ
http-message-sig
รูปแบบคำขอแบบเต็ม
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"}
แยกวิเคราะห์และตรวจสอบคำขอ
- การตรวจสอบสิทธิ์เซสชัน: ตรวจสอบคุกกี้เซสชันของบุคคลที่หนึ่งที่ส่งมา พร้อมกับคำขอ ต้องมีการตรวจสอบสิทธิ์ผู้ใช้
Sec-Fetch-Destส่วนหัว: ตั้งค่าเป็นemail-verification- ลายเซ็นข้อความ HTTP: ยืนยันลายเซ็นคำขอโดยใช้
คีย์สาธารณะชั่วคราวใน
Signature-KeyและตรวจสอบContent-Digest - เพย์โหลด: เนื้อหา JSON มีสตริง
emailที่ขอสำหรับการยืนยัน
การตอบกลับการออกบัตร
เมื่อยืนยันเซสชันและโทเค็นคำขอสำเร็จแล้ว ให้สร้าง JWT การเปิดเผยข้อมูลแบบเลือก (SD-JWT) ที่ลงนามแล้วซึ่งส่งคืนเป็น JSON โดยใช้ไลบรารีที่เหมาะสมสำหรับแพลตฟอร์มของคุณ เช่น สำหรับ Node คุณสามารถใช้
@sd-jwt/core และ
jose
รูปแบบเพย์โหลดดิบควรมีลักษณะดังนี้
{
"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
}
สร้าง ลงนาม และส่งคืนโทเค็น
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);
เนื้อหาการตอบกลับที่ได้จะมีลักษณะคล้ายกับตัวอย่างต่อไปนี้
{
"issuance_token": "tOkEn123tOkEn123tOkEn123...~"
}
หลังจากสร้างและลงนามโทเค็นการยืนยันทางอีเมลแล้ว เบราว์เซอร์จะส่งโทเค็นนี้ ไปยังเว็บไซต์ที่ยืนยันเพื่อตรวจสอบความถูกต้อง
การแก้ปัญหา
หากเบราว์เซอร์ไม่ติดต่อปลายทางหรือปฏิเสธโทเค็นที่ออก ให้ตรวจสอบปัญหาที่พบได้ทั่วไปต่อไปนี้
เบราว์เซอร์จะไม่โทรหา accounts_endpoint หรือ issuance_endpoint
- ไม่ได้ตั้งค่าสถานะการเข้าสู่ระบบ: Chrome จะค้นหาข้อมูลปลายทางก็ต่อเมื่อทราบว่าผู้ใช้ลงชื่อเข้าใช้แล้ว
ตรวจสอบว่าขั้นตอนการลงชื่อเข้าใช้ตั้งค่าส่วนหัว
Set-Login: logged-inHTTP หรือเรียกใช้navigator.login.setStatus("logged-in") - คุกกี้เซสชันถูกบล็อก (
SameSite=None): เบราว์เซอร์จะดึงข้อมูลaccounts_endpointและissuance_endpointจากบุคคลที่สามที่เชื่อถือได้ใน เว็บไซต์อื่น เนื่องจากเป็นคำขอข้ามเว็บไซต์ คุกกี้เซสชัน ต้องมีSameSite=None; SecureSameSite=Laxคุกกี้จะทํางานเมื่อ ทดสอบในเครื่องมือยืนยันแบบเว็บไซต์เดียวกัน แต่จะถูกละเว้นในคําขอข้ามเว็บไซต์ - การค้นพบหรือบัญชีไม่ตรงกัน: ตรวจสอบว่า
_email-verification.<email-domain>แสดงระเบียน TXT รายการเดียว (iss=<issuer-domain>โดยไม่มีhttps://) ทั้ง 2 จุดปลายทาง.well-knownแสดงContent-Type: application/jsonและการตอบกลับaccounts_endpointมีบัญชีที่มีemailตรงกับที่อยู่ที่ป้อน
คำขอออกบัตรไม่ผ่านการตรวจสอบ
- การสะกดส่วนหัว
Sec-Fetch-Dest: Chrome 154 ขึ้นไปจะส่งSec-Fetch-Dest: email-verification(มีขีดกลาง) ส่วน Chrome 153 จะส่งemailverification(ไม่มีขีดกลาง) ยอมรับทั้ง 2 ค่าในระหว่างการเปิดตัว - ลายเซ็นข้อความ HTTP (
@authority) ไม่ตรงกันหลังพร็อกซี: เมื่อ ยืนยันลายเซ็น RFC 9421 คอมโพเนนต์@authorityจะแสดงโฮสต์ที่หันหน้าสู่สาธารณะ หากเซิร์ฟเวอร์อยู่หลังพร็อกซีผกผันหรือตัวจัดสรรภาระงาน ให้สร้าง URL การยืนยันใหม่โดยใช้X-Forwarded-Host(หรือต้นทางสาธารณะ) แทนชื่อโฮสต์ภายใน และคำนวณContent-Digestในไบต์ของเนื้อหาคำขอแบบดิบก่อนการแยกวิเคราะห์ JSON
เบราว์เซอร์ปฏิเสธ issuance_token ที่ส่งคืน
- อ้างสิทธิ์
emailที่แก้ไขหรือแปลงเป็นรูปแบบมาตรฐาน: ตั้งแต่ Chrome 156 เป็นต้นไป เบราว์เซอร์จะ ตรวจสอบว่าการอ้างสิทธิ์ EVTemailตรงกับemailที่ขอ แบบไบต์ต่อไบต์ หากแบ็กเอนด์แปลงที่อยู่ให้เป็นรูปแบบบัญชีที่แน่นอน (เช่น แสดงFirst.Last@example.comเมื่อมีการขอfirst.LAST@example.com) Chrome จะทิ้งโทเค็น จับคู่คำขอกับบัญชีของผู้ใช้ แต่ให้แสดงสตริงemailที่ตรงกัน ซึ่งได้รับในเนื้อหาของคำขอ - ไม่มีเครื่องหมายตัวหนอน (
~) ตอนท้าย: แม้จะไม่มีการเปิดเผยข้อมูลใดๆ แต่issuance_tokenต้องเป็น SD-JWT ที่ถูกต้องซึ่งลงท้ายด้วยเครื่องหมายตัวหนอน (<Issuer-signed-JWT>~) เพื่อให้เบราว์เซอร์สามารถต่อท้าย<KB-JWT>ได้ issหรือcnf.jwkไม่ตรงกัน: ตรวจสอบว่าการอ้างสิทธิ์ EVTissเป็นต้นทาง HTTPS ที่แน่นอน (https://<issuer-domain>ไม่มีเครื่องหมายทับต่อท้าย) ซึ่งตรงกับข้อมูลเมตา.well-known/email-verificationและcnf.jwkฝังคีย์สาธารณะชั่วคราวของเบราว์เซอร์จากส่วนหัวSignature-Key