การติดตั้งใช้งานผู้ให้บริการอีเมล (ผู้ออก)

ดูรายละเอียดเพิ่มเติมได้โดยดูโค้ดการสาธิตผู้ให้บริการอีเมลจำลอง และดูขั้นตอนของผู้ให้บริการในข้อเสนอ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-in HTTP หรือเรียกใช้ navigator.login.setStatus("logged-in")
  • คุกกี้เซสชันถูกบล็อก (SameSite=None): เบราว์เซอร์จะดึงข้อมูล accounts_endpoint และ issuance_endpoint จากบุคคลที่สามที่เชื่อถือได้ใน เว็บไซต์อื่น เนื่องจากเป็นคำขอข้ามเว็บไซต์ คุกกี้เซสชัน ต้องมี SameSite=None; Secure SameSite=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 เป็นต้นไป เบราว์เซอร์จะ ตรวจสอบว่าการอ้างสิทธิ์ EVT email ตรงกับ emailที่ขอ แบบไบต์ต่อไบต์ หากแบ็กเอนด์แปลงที่อยู่ให้เป็นรูปแบบบัญชีที่แน่นอน (เช่น แสดง First.Last@example.com เมื่อมีการขอ first.LAST@example.com) Chrome จะทิ้งโทเค็น จับคู่คำขอกับบัญชีของผู้ใช้ แต่ให้แสดงสตริง email ที่ตรงกัน ซึ่งได้รับในเนื้อหาของคำขอ
  • ไม่มีเครื่องหมายตัวหนอน (~) ตอนท้าย: แม้จะไม่มีการเปิดเผยข้อมูลใดๆ แต่ issuance_token ต้องเป็น SD-JWT ที่ถูกต้องซึ่งลงท้ายด้วยเครื่องหมายตัวหนอน (<Issuer-signed-JWT>~) เพื่อให้เบราว์เซอร์สามารถต่อท้าย <KB-JWT> ได้
  • issหรือ cnf.jwk ไม่ตรงกัน: ตรวจสอบว่าการอ้างสิทธิ์ EVT iss เป็นต้นทาง HTTPS ที่แน่นอน (https://<issuer-domain> ไม่มีเครื่องหมายทับต่อท้าย) ซึ่งตรงกับข้อมูลเมตา .well-known/email-verification และ cnf.jwk ฝังคีย์สาธารณะชั่วคราวของเบราว์เซอร์จากส่วนหัว Signature-Key