คู่มือนี้จะอธิบายรายละเอียดวิธีใช้การตรวจสอบสิทธิ์ผู้ใช้ที่กลับมาโดยใช้ข้อมูลเข้าสู่ระบบที่ค้นพบได้ ทั้งผ่านทริกเกอร์ปุ่มที่ชัดเจนและคำแนะนำการป้อนข้อความอัตโนมัติในเบราว์เซอร์ที่ราบรื่น (UI แบบมีเงื่อนไข)
ฝั่งเซิร์ฟเวอร์
การสร้างตัวเลือก
สร้างปลายทางที่สร้างพารามิเตอร์คำขอ WebAuthn โดยใช้ไลบรารีที่ตรวจสอบแล้วตามมาตรฐาน
- ใช้รหัส RP ที่กำหนดไว้ล่วงหน้า: ใช้รหัส RP ที่เหมาะสมซึ่งกำหนดไว้ล่วงหน้าเป็นสตริงคงที่
- สร้างคำถาม: สร้างบัฟเฟอร์แบบสุ่มที่มีเอนโทรปีสูงและปลอดภัยในการเข้ารหัส จัดเก็บไว้อย่างปลอดภัยในเซสชันของผู้ใช้ และเข้ารหัสเป็น Base64URL
- การแมปข้อมูลเข้าสู่ระบบที่ค้นพบได้: ระบุอาร์เรย์ที่ว่างเปล่า
[]สำหรับallowCredentialsซึ่งจะขอข้อมูลเข้าสู่ระบบที่ค้นพบได้ หมายความว่าผู้ใช้ไม่จำเป็นต้องป้อนชื่อผู้ใช้ก่อน ผู้ให้บริการพาสคีย์จะแสดงบัญชีที่พร้อมใช้งาน - ระดับการยืนยันตัวตนของผู้ใช้: ตั้งค่าเป็น
userVerification: "preferred"(หรือ"required"หากนโยบายการปฏิบัติตามข้อกำหนดขององค์กรกำหนดไว้อย่างชัดเจน)- ระดับข้อจำกัด
userVerificationที่ขอต้องคงอยู่ในระเบียนเซสชันของเซิร์ฟเวอร์ที่ปลายทางตัวเลือก แทนที่จะส่งกลับจากไคลเอ็นต์ผ่านสตริงการค้นหา ซึ่งช่วยให้ปลายทางการยืนยันบังคับใช้ข้อจำกัดการจับคู่ที่เข้มงวดได้อย่างปลอดภัยโดยไม่มีความเสี่ยงที่ไคลเอ็นต์จะถูกบิดเบือนข้อมูล
- ระดับข้อจำกัด
// Options generation example (discoverable flow)
const options = {
challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
rpId: "example.com",
allowCredentials: [], // Request discoverable passkeys
userVerification: "preferred",
};
// Persist expected UV level to user session
req.session.expectedUserVerification = "preferred";
ปลายทางการยืนยัน
ยืนยันการยืนยันที่ไคลเอ็นต์แสดงผลอย่างปลอดภัยเพื่อตรวจสอบสิทธิ์ผู้ใช้ โดยทำดังนี้
- ตรวจสอบคำถามของเซสชัน: บังคับใช้การจับคู่คำถามที่เข้มงวดระหว่างการตอบกลับของไคลเอ็นต์กับคำถามที่คาดไว้ซึ่งจัดเก็บไว้ในเซสชัน
- บังคับใช้ค่ากำหนด UV:
- อนุญาตให้ใช้เครื่องมือตรวจสอบสิทธิ์ที่ไม่มี UV (เช่น ปิดใช้การล็อกหน้าจอของเครื่องมือตรวจสอบสิทธิ์) หาก
expectedUserVerificationของเซสชันขอ"preferred"โดยส่งrequireUserVerification: falseไปยังไลบรารีการยืนยันฝั่งเซิร์ฟเวอร์ หากมีการขอ"required"ให้บังคับใช้การป้อนข้อมูลไบโอเมตริก/PIN อย่างเคร่งครัด
- อนุญาตให้ใช้เครื่องมือตรวจสอบสิทธิ์ที่ไม่มี UV (เช่น ปิดใช้การล็อกหน้าจอของเครื่องมือตรวจสอบสิทธิ์) หาก
- ข้อผิดพลาดเกี่ยวกับเซิร์ฟเวอร์ 404: หากไม่พบรหัสข้อมูลเข้าสู่ระบบที่ไคลเอ็นต์แสดงในฐานข้อมูล ให้แสดงข้อผิดพลาด HTTP
404เพื่อให้ไคลเอ็นต์ทริกเกอร์ Signal API ได้
ตรรกะฝั่งไคลเอ็นต์
คำอธิบายประกอบแบบฟอร์ม HTML
ใส่คำอธิบายประกอบอินพุตชื่อผู้ใช้และรหัสผ่านเพื่อใช้ประโยชน์จาก UI แบบมีเงื่อนไขในตัว โทเค็นการเติมข้อความอัตโนมัติจะรวมพารามิเตอร์ข้อกำหนดเฉพาะของ WebAuthn และการโฟกัสอัตโนมัติจะทริกเกอร์ป๊อปอัปการป้อนข้อความอัตโนมัติของเบราว์เซอร์ทันทีเมื่อมีการโฟกัสอินพุต
<!-- Autocomplete tokens must contain webauthn space-separated -->
<form id="signin-form">
<input
type="text"
name="username"
autocomplete="username webauthn"
autofocus
data-testid="username-field"
/>
<input type="password" name="password" autocomplete="current-password" />
<button type="submit">Sign in</button>
</form>
ขั้นตอนการใช้ปุ่มที่ชัดเจน
ทริกเกอร์การตรวจสอบสิทธิ์ด้วยพาสคีย์เมื่อผู้ใช้คลิกปุ่ม "ลงชื่อเข้าใช้ด้วยพาสคีย์" ยกเลิกการเรียกใช้การป้อนข้อความอัตโนมัติในแบบฟอร์ม (Conditional Get) ที่ดำเนินอยู่ก่อนที่จะเรียกใช้ข้อความแจ้งพาสคีย์
ขั้นตอนการสื่อกลางแบบมีเงื่อนไข (การป้อนข้อความอัตโนมัติในแบบฟอร์ม)
เปิดใช้งานคำแนะนำการป้อนข้อความอัตโนมัติในแบบฟอร์มเมื่อโหลดหน้าเว็บเพื่อเสนอการตรวจสอบสิทธิ์ด้วยพาสคีย์โดยตรงเมื่อผู้ใช้โฟกัสที่ช่องลงชื่อเข้าใช้
- ตรวจหาฟีเจอร์: เรียกใช้
PublicKeyCredential.getClientCapabilities()เมื่อโหลดหน้าเว็บ และข้ามการลงชื่อเข้าใช้ด้วยพาสคีย์หากconditionalGetไม่พร้อมใช้งาน - ตัวเลือกการถอดรหัส: ถอดรหัสออบเจ็กต์ JSON ของข้อมูลเข้าสู่ระบบที่ดึงมาด้วย
PublicKeyCredential.parseRequestOptionsFromJSON() - เรียกใช้ GET แบบมีเงื่อนไข: เรียก
navigator.credentials.get()ด้วยmediation: "conditional"และส่งสัญญาณAbortControllerซึ่งจะลงทะเบียนการป้อนข้อความอัตโนมัติโดยไม่มีเสียงและไม่แสดงกล่องโต้ตอบพาสคีย์ - การแยกข้อยกเว้น Try/Catch: รวมการเรียก
navigator.credentials.getไว้ในบล็อก Try/Catch:NotAllowedError: ผู้ใช้ยกเลิกหรือหมดเวลาในข้อความแจ้งให้เข้าสู่ระบบด้วยพาสคีย์AbortError: คำขอตรวจสอบสิทธิ์ถูกยกเลิกโดยใช้โปรแกรม
- Call Signal API: รวมการเรียก
fetch()การยืนยันเซิร์ฟเวอร์ไว้ในบล็อก try/catch ดังนี้- แสดงข้อความแสดงข้อผิดพลาดเพื่อให้ผู้ใช้เข้าใจว่าเกิดข้อผิดพลาดอะไรขึ้น
- เรียกใช้
signalUnknownCredential()เฉพาะเมื่อเซิร์ฟเวอร์ตอบกลับอย่างชัดเจนด้วยสถานะ HTTP404(ไม่พบข้อมูลเข้าสู่ระบบ) และผู้ใช้ไม่ได้รับการตรวจสอบสิทธิ์ - พารามิเตอร์
credentialIdที่ส่งไปยังsignalUnknownCredential()ต้องเป็นสตริงรหัสข้อมูลเข้าสู่ระบบที่เข้ารหัส Base64URL อย่างเคร่งครัด (เช่นencoded.id) ไม่ใช่ออบเจ็กต์ ArrayBuffer ดิบcredential.rawId
- เข้ารหัสการตอบกลับ: เข้ารหัสข้อมูลเข้าสู่ระบบ
AuthenticatorAssertionResponseด้วย.toJSON()ก่อนส่งไปยังเซิร์ฟเวอร์เพื่อยืนยัน
// optionsFetch and loginVerifyFetch are app-defined HTTP methods
import { optionsFetch, loginVerifyFetch } from "./api.js";
let autofillAbortController = new AbortController();
async function initializeConditionalAutofill() {
// Feature detect Conditional Get autofill support
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (capabilities.conditionalGet === true) {
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
try {
// Initiate Conditional UI form autofill suggestions
const credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
mediation: "conditional",
});
// Segregated verification fetch
const encoded = credential.toJSON();
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
if (PublicKeyCredential.signalUnknownCredential) {
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id,
});
}
}
} catch (err) {
// Silently swallow expected client WebAuthn exceptions
if (["NotAllowedError", "AbortError"].includes(err.name)) {
return;
}
console.error("Unexpected conditional get error:", err);
}
}
}
async function triggerButtonAuthentication() {
// Abort any pending Conditional Get call to prevent passkey prompt collisions
autofillAbortController.abort();
autofillAbortController = new AbortController(); // Reset controller for next triggers
const loginOptionsJSON = await optionsFetch();
const publicKey =
PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);
let credential;
try {
// Passkey explicit prompt trigger
credential = await navigator.credentials.get({
publicKey,
signal: autofillAbortController.signal,
});
} catch (err) {
if (err.name === "NotAllowedError") {
console.log("User cancelled passkey login.");
} else if (err.name === "AbortError") {
console.log("The authentication operation was aborted.");
}
// Re-arm Conditional autofill Suggestions after cancelled explicit button prompts
initializeConditionalAutofill();
return; // Safe exit
}
// Segregated verification try/catch (HTTP 404 trigger)
const encoded = credential.toJSON();
try {
const response = await loginVerifyFetch(encoded);
if (!response.ok && response.status === 404) {
// Note: this code path runs pre-authentication, satisfying the unauth precondition
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encoded.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Verification request error:", serverErr);
}
}
// Trigger Conditional Get on load
window.addEventListener("DOMContentLoaded", initializeConditionalAutofill);
กลยุทธ์สำรอง
ฟีเจอร์สำรองสำหรับการตรวจหาฟีเจอร์พาสคีย์
ติดตั้ง "webauthn-polyfills" และนำเข้าในบริบทเสมอ
พิจารณาว่าตราบใดที่ระบบรองรับ PublicKeyCredential ระบบก็จะรองรับ PublicKeyCredential.getClientCapabilities ด้วย
import 'webauthn-polyfills';
การซิงค์ API ของ Signal สำรอง
WebAuthn Signal API (webauthn-signals) เป็นการเพิ่มประสิทธิภาพแบบต่อเนื่องที่ใช้เพื่อซิงค์เครื่องมือจัดการรหัสผ่านกับสถานะข้อมูลเข้าสู่ระบบของเซิร์ฟเวอร์
- ประสบการณ์สำรอง: จำกัดการเข้าถึงผ่าน
if (PublicKeyCredential.signalUnknownCredential)หากไม่รองรับ ระบบจะข้ามการซิงค์การยืนยันในเบื้องหลังอย่างราบรื่นโดยไม่แสดงข้อยกเว้นของเบราว์เซอร์
Easy JSON Serialization Fallback
ติดตั้ง "webauthn-polyfills" และนำเข้าในบริบทเสมอ
โปรดทราบว่าตราบใดที่ระบบรองรับ PublicKeyCredential ระบบก็จะรองรับ PublicKeyCredential.parseRequestOptionsFromJSON และ PublicKeyCredential.prototype.toJSON ด้วย
import 'webauthn-polyfills';