ใช้พาสคีย์กับการป้อนข้อความอัตโนมัติของแบบฟอร์มในเว็บแอป

1. ก่อนเริ่มต้น

การใช้พาสคีย์แทนรหัสผ่านเป็นวิธีที่ยอดเยี่ยมสำหรับเว็บไซต์ในการทำให้บัญชีผู้ใช้ปลอดภัย เรียบง่าย และใช้งานง่ายยิ่งขึ้น เมื่อใช้พาสคีย์ ผู้ใช้จะลงชื่อเข้าใช้เว็บไซต์หรือแอปได้โดยใช้ฟีเจอร์ล็อกหน้าจอของอุปกรณ์ เช่น ลายนิ้วมือ ใบหน้า หรือ PIN ของอุปกรณ์ ต้องสร้างพาสคีย์ เชื่อมโยงกับบัญชีผู้ใช้ และจัดเก็บคีย์สาธารณะไว้ในเซิร์ฟเวอร์ก่อนที่ผู้ใช้จะลงชื่อเข้าใช้ด้วยพาสคีย์ได้

ใน Codelab นี้ คุณจะเปลี่ยนการลงชื่อเข้าใช้ด้วยชื่อผู้ใช้และรหัสผ่านแบบฟอร์มพื้นฐานเป็นการลงชื่อเข้าใช้ที่รองรับพาสคีย์และมีองค์ประกอบต่อไปนี้

  • ปุ่มที่สร้างพาสคีย์หลังจากที่ผู้ใช้ลงชื่อเข้าใช้
  • UI ที่แสดงรายการพาสคีย์ที่ลงทะเบียน
  • แบบฟอร์มลงชื่อเข้าใช้ที่มีอยู่ซึ่งช่วยให้ผู้ใช้ลงชื่อเข้าใช้ด้วยพาสคีย์ที่ลงทะเบียนไว้ผ่านการป้อนข้อความอัตโนมัติในแบบฟอร์ม

ข้อกำหนดเบื้องต้น

  • ความเข้าใจพื้นฐานเกี่ยวกับ JavaScript
  • ความเข้าใจพื้นฐานเกี่ยวกับพาสคีย์
  • ความเข้าใจพื้นฐานเกี่ยวกับ Web Authentication API (WebAuthn)

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้างพาสคีย์
  • วิธีตรวจสอบสิทธิ์ผู้ใช้ด้วยพาสคีย์
  • วิธีอนุญาตให้แบบฟอร์มแนะนำพาสคีย์เป็นตัวเลือกการลงชื่อเข้าใช้

สิ่งที่คุณต้องมี

ชุดค่าผสมของอุปกรณ์อย่างใดอย่างหนึ่งต่อไปนี้

  • Google Chrome ในอุปกรณ์ Android ที่ใช้ Android 9 ขึ้นไป โดยควรมีเซ็นเซอร์ไบโอเมตริก
  • Chrome ที่มีอุปกรณ์ Windows ซึ่งใช้ Windows 10 ขึ้นไป
  • Safari 16 ขึ้นไปที่มี iPhone ที่ใช้ iOS 16 ขึ้นไป หรือ iPad ที่ใช้ iPadOS 16 ขึ้นไป
  • Safari 16 ขึ้นไปหรือ Chrome ที่มีอุปกรณ์เดสก์ท็อปของ Apple ซึ่งใช้ macOS Ventura ขึ้นไป

2. ตั้งค่า

ในโค้ดแล็บนี้ คุณจะได้ใช้บริการที่ชื่อว่า Glitch ซึ่งช่วยให้คุณแก้ไขโค้ดฝั่งไคลเอ็นต์และฝั่งเซิร์ฟเวอร์ด้วย JavaScript และนำไปใช้งานจากเบราว์เซอร์ได้โดยเฉพาะ

เปิดโปรเจ็กต์

  1. เปิดโปรเจ็กต์ใน Glitch
  2. คลิกรีมิกซ์เพื่อแยกโปรเจ็กต์ Glitch
  3. ในเมนูการนำทางที่ด้านล่างของ Glitch ให้คลิกตัวอย่าง > ดูตัวอย่างในหน้าต่างใหม่ แท็บอื่นจะเปิดขึ้นในเบราว์เซอร์

ปุ่มแสดงตัวอย่างในหน้าต่างใหม่ในเมนูการนำทางที่ด้านล่างของ Glitch

ตรวจสอบสถานะเริ่มต้นของเว็บไซต์

  1. ในแท็บแสดงตัวอย่าง ให้ป้อนชื่อผู้ใช้แบบสุ่ม แล้วคลิกถัดไป
  2. ป้อนรหัสผ่านแบบสุ่ม แล้วคลิกลงชื่อเข้าใช้ ระบบจะไม่สนใจรหัสผ่าน แต่คุณจะยังคงได้รับการตรวจสอบสิทธิ์และไปที่หน้าแรก
  3. หากต้องการเปลี่ยนชื่อที่แสดง ให้ดำเนินการได้เลย คุณทำได้เพียงเท่านี้ในสถานะเริ่มต้น
  4. คลิกออกจากระบบ

ในสถานะนี้ ผู้ใช้ต้องป้อนรหัสผ่านทุกครั้งที่เข้าสู่ระบบ คุณเพิ่มการรองรับพาสคีย์ลงในแบบฟอร์มนี้เพื่อให้ผู้ใช้ลงชื่อเข้าใช้ได้ด้วยฟังก์ชันการล็อกหน้าจอของอุปกรณ์ คุณลองใช้สถานะสุดท้ายได้ที่ https://passkeys-codelab.glitch.me/

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีการทำงานของพาสคีย์ได้ที่พาสคีย์ทำงานอย่างไร

3. เพิ่มความสามารถในการสร้างพาสคีย์

หากต้องการให้ผู้ใช้ตรวจสอบสิทธิ์ด้วยพาสคีย์ได้ คุณต้องให้สิทธิ์ผู้ใช้ในการสร้างและลงทะเบียนพาสคีย์ รวมถึงจัดเก็บคีย์สาธารณะของพาสคีย์ไว้ในเซิร์ฟเวอร์

กล่องโต้ตอบการยืนยันผู้ใช้พาสคีย์จะปรากฏขึ้นเมื่อสร้างพาสคีย์

คุณต้องการอนุญาตให้สร้างพาสคีย์หลังจากที่ผู้ใช้เข้าสู่ระบบด้วยรหัสผ่าน และเพิ่ม UI ที่ช่วยให้ผู้ใช้สร้างพาสคีย์และดูรายการพาสคีย์ที่ลงทะเบียนทั้งหมดในหน้า /home ในส่วนถัดไป คุณจะสร้างฟังก์ชันที่สร้างและลงทะเบียนพาสคีย์

สร้างฟังก์ชัน registerCredential()

  1. ใน Glitch ให้ไปที่ไฟล์ public/client.js แล้วเลื่อนไปที่ท้ายไฟล์
  2. หลังจากความคิดเห็นที่เกี่ยวข้อง ให้เพิ่มฟังก์ชัน registerCredential() ต่อไปนี้

public/client. js

// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {

  // TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.

  // TODO: Add an ability to create a passkey: Create a credential.

  // TODO: Add an ability to create a passkey: Register the credential to the server endpoint.

};

ฟังก์ชันนี้จะสร้างและลงทะเบียนพาสคีย์ในเซิร์ฟเวอร์

รับการท้าทายและตัวเลือกอื่นๆ จากปลายทางของเซิร์ฟเวอร์

ก่อนที่จะสร้างพาสคีย์ คุณต้องขอพารามิเตอร์เพื่อส่งใน WebAuthn จากเซิร์ฟเวอร์ ซึ่งรวมถึงการท้าทาย WebAuthn คือ API ของเบราว์เซอร์ที่ช่วยให้ผู้ใช้สร้างพาสคีย์และตรวจสอบสิทธิ์ผู้ใช้ด้วยพาสคีย์ได้ โชคดีที่คุณมีปลายทางเซิร์ฟเวอร์ที่ตอบสนองด้วยพารามิเตอร์ดังกล่าวในโค้ดแล็บนี้อยู่แล้ว

  • หากต้องการรับคำท้าและตัวเลือกอื่นๆ จากปลายทางของเซิร์ฟเวอร์ ให้เพิ่มโค้ดต่อไปนี้ลงในเนื้อหาของฟังก์ชัน registerCredential() หลังจากความคิดเห็นที่เกี่ยวข้อง

public/client.js

// TODO: Add an ability to create a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/registerRequest');

ข้อมูลโค้ดต่อไปนี้มีตัวเลือกตัวอย่างที่คุณได้รับจากเซิร์ฟเวอร์

{
  challenge: *****,
  rp: {
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },  
  pubKeyCredParams: [{
    alg: -7, type: "public-key"
  },{
    alg: -257, type: "public-key"
  }],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal', 'hybrid'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
}

โปรโตคอลระหว่างเซิร์ฟเวอร์และไคลเอ็นต์ไม่ได้เป็นส่วนหนึ่งของข้อกำหนด WebAuthn อย่างไรก็ตาม เซิร์ฟเวอร์ของ Codelab นี้ได้รับการออกแบบมาให้แสดงผล JSON ที่คล้ายกับพจนานุกรม PublicKeyCredentialCreationOptions ที่ส่งไปยัง WebAuthn navigator.credentials.create() API มากที่สุด

ตารางต่อไปนี้ไม่ได้ครอบคลุมทั้งหมด แต่มีพารามิเตอร์ที่สำคัญในPublicKeyCredentialCreationOptionsพจนานุกรม

พารามิเตอร์

คำอธิบาย

challenge

คำท้าที่เซิร์ฟเวอร์สร้างขึ้นในออบเจ็กต์ ArrayBuffer สำหรับการลงทะเบียนนี้ ต้องระบุ แต่ไม่ได้ใช้ในระหว่างการลงทะเบียน เว้นแต่จะทำการรับรอง ซึ่งเป็นหัวข้อขั้นสูงที่ไม่ได้กล่าวถึงใน Codelab นี้

user.id

รหัสที่ไม่ซ้ำกันของผู้ใช้ ค่านี้ต้องเป็นออบเจ็กต์ ArrayBuffer ที่ไม่มีข้อมูลระบุตัวบุคคล เช่น อีเมลหรือชื่อผู้ใช้ ค่าแบบสุ่มขนาด 16 ไบต์ที่สร้างขึ้นต่อบัญชีจะทํางานได้ดี

user.name

ฟิลด์นี้ควรมีตัวระบุที่ไม่ซ้ำกันสำหรับบัญชีที่ผู้ใช้จดจำได้ เช่น อีเมลหรือชื่อผู้ใช้ โดยจะแสดงในตัวเลือกบัญชี (หากใช้ชื่อผู้ใช้ ให้ใช้ค่าเดียวกับการตรวจสอบสิทธิ์ด้วยรหัสผ่าน)

user.displayName

ฟิลด์นี้เป็นชื่อบัญชีที่เรียกง่ายซึ่งผู้ใช้จะระบุหรือไม่ก็ได้ ไม่จำเป็นต้องไม่ซ้ำกันและอาจเป็นชื่อที่ผู้ใช้เลือก หากเว็บไซต์ไม่มีค่าที่เหมาะสมที่จะรวมไว้ที่นี่ ให้ส่งสตริงว่าง ซึ่งอาจแสดงในตัวเลือกบัญชี ทั้งนี้ขึ้นอยู่กับเบราว์เซอร์

rp.id

รหัสผู้ให้บริการ (RP) คือโดเมน เว็บไซต์สามารถระบุโดเมนหรือคำต่อท้ายที่จดทะเบียนได้ ตัวอย่างเช่น หากต้นทางของ RP คือ https://login.example.com:1337 รหัส RP อาจเป็น login.example.com หรือ example.com หากระบุรหัส RP เป็น example.com ผู้ใช้จะตรวจสอบสิทธิ์ได้ใน login.example.com หรือในโดเมนย่อยอื่นๆ ของ example.com

pubKeyCredParams

ฟิลด์นี้ระบุอัลกอริทึมคีย์สาธารณะที่ RP รองรับ เราขอแนะนำให้ตั้งค่าเป็น [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}] ซึ่งระบุการรองรับ ECDSA ด้วย P-256 และ RSA PKCS#1 และการรองรับสิ่งเหล่านี้จะทำให้ครอบคลุมทั้งหมด

excludeCredentials

แสดงรายการรหัสข้อมูลเข้าสู่ระบบที่ลงทะเบียนแล้วเพื่อป้องกันการลงทะเบียนอุปกรณ์เดียวกัน 2 ครั้ง หากระบุไว้ สมาชิก transports ควรมีผลลัพธ์ของการเรียกฟังก์ชัน getTransports() ในระหว่างการลงทะเบียนข้อมูลเข้าสู่ระบบแต่ละรายการ

authenticatorSelection.authenticatorAttachment

ตั้งค่าเป็น "platform" ซึ่งหมายความว่าคุณต้องการเครื่องมือตรวจสอบสิทธิ์ที่ฝังอยู่ในอุปกรณ์แพลตฟอร์ม เพื่อให้ระบบไม่ต้องแจ้งให้ผู้ใช้เสียบอุปกรณ์ เช่น คีย์ความปลอดภัยแบบ USB

authenticatorSelection.requireResidentKey

ตั้งค่าเป็นค่าบูลีน true คุณใช้ข้อมูลเข้าสู่ระบบที่ค้นพบได้ (คีย์ที่อยู่ในอุปกรณ์) โดยไม่ต้องให้เซิร์ฟเวอร์ระบุรหัสของข้อมูลเข้าสู่ระบบ จึงเข้ากันได้กับการป้อนข้อความอัตโนมัติ

authenticatorSelection.userVerification

ตั้งค่าเป็น "preferred" หรือละเว้นไว้เนื่องจากเป็นค่าเริ่มต้น ซึ่งระบุว่าการยืนยันตัวตนของผู้ใช้ที่ใช้การล็อกหน้าจอของอุปกรณ์เป็น "required", "preferred" หรือ "discouraged" การตั้งค่าเป็นค่า "preferred" จะขอการยืนยันผู้ใช้เมื่ออุปกรณ์ทำได้

สร้างข้อมูลเข้าสู่ระบบ

  1. ในส่วนเนื้อหาของฟังก์ชัน registerCredential() หลังจากความคิดเห็นที่เกี่ยวข้อง ให้แปลงพารามิเตอร์บางรายการที่เข้ารหัสด้วย Base64URL กลับเป็นไบนารี โดยเฉพาะสตริง user.id และ challenge รวมถึงอินสแตนซ์ของสตริง id ที่รวมอยู่ในอาร์เรย์ excludeCredentials ดังนี้

public/client.js

// TODO: Add an ability to create a passkey: Create a credential.
// Base64URL decode some values.
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);

if (options.excludeCredentials) {
  for (let cred of options.excludeCredentials) {
    cred.id = base64url.decode(cred.id);
  }
}
  1. ในบรรทัดถัดไป ให้ตั้งค่า authenticatorSelection.authenticatorAttachment เป็น "platform" และ authenticatorSelection.requireResidentKey เป็น true ซึ่งจะอนุญาตให้ใช้เฉพาะเครื่องมือตรวจสอบสิทธิ์แพลตฟอร์ม (อุปกรณ์เอง) ที่มีความสามารถของข้อมูลเข้าสู่ระบบที่ค้นพบได้

public/client.js

// Use platform authenticator and discoverable credential.
options.authenticatorSelection = {
  authenticatorAttachment: 'platform',
  requireResidentKey: true
}
  1. ในบรรทัดถัดไป ให้เรียกใช้เมธอด navigator.credentials.create() เพื่อสร้างข้อมูลเข้าสู่ระบบ

public/client.js

// Invoke the WebAuthn create() method.
const cred = await navigator.credentials.create({
  publicKey: options,
});

การเรียกนี้ทำให้เบราว์เซอร์พยายามยืนยันตัวตนของผู้ใช้ด้วยการล็อกหน้าจอของอุปกรณ์

ลงทะเบียนข้อมูลเข้าสู่ระบบกับปลายทางของเซิร์ฟเวอร์

หลังจากที่ผู้ใช้ยืนยันตัวตนแล้ว ระบบจะสร้างและจัดเก็บพาสคีย์ เว็บไซต์จะได้รับออบเจ็กต์ข้อมูลเข้าสู่ระบบที่มีคีย์สาธารณะซึ่งคุณสามารถส่งไปยังเซิร์ฟเวอร์เพื่อลงทะเบียนพาสคีย์ได้

ข้อมูลโค้ดต่อไปนี้มีตัวอย่างออบเจ็กต์ข้อมูลเข้าสู่ระบบ

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "attestationObject": *****,
    "transports": ["internal", "hybrid"]
  },
  "authenticatorAttachment": "platform"
}

ตารางต่อไปนี้ไม่ได้ครอบคลุมทั้งหมด แต่มีพารามิเตอร์ที่สำคัญในออบเจ็กต์ PublicKeyCredential

พารามิเตอร์

คำอธิบาย

id

รหัสที่เข้ารหัส Base64URL ของพาสคีย์ที่สร้างขึ้น รหัสนี้ช่วยให้เบราว์เซอร์พิจารณาได้ว่ามีพาสคีย์ที่ตรงกันในอุปกรณ์เมื่อมีการตรวจสอบสิทธิ์หรือไม่ ค่านี้ต้องจัดเก็บไว้ในฐานข้อมูลในแบ็กเอนด์

rawId

ArrayBuffer เวอร์ชันออบเจ็กต์ของรหัสข้อมูลเข้าสู่ระบบ

response.clientDataJSON

ออบเจ็กต์ ArrayBuffer ที่เข้ารหัสข้อมูลไคลเอ็นต์

response.attestationObject

ออบเจ็กต์การรับรองที่เข้ารหัส ArrayBuffer โดยมีข้อมูลสำคัญ เช่น รหัส RP, แฟล็ก และคีย์สาธารณะ

response.transports

รายการการรับส่งที่อุปกรณ์รองรับ: "internal" หมายความว่าอุปกรณ์รองรับพาสคีย์ "hybrid" หมายความว่ายังรองรับการตรวจสอบสิทธิ์ในอุปกรณ์เครื่องอื่นด้วย

authenticatorAttachment

แสดง "platform" เมื่อสร้างข้อมูลเข้าสู่ระบบนี้ในอุปกรณ์ที่ใช้พาสคีย์ได้

หากต้องการส่งออบเจ็กต์ข้อมูลเข้าสู่ระบบไปยังเซิร์ฟเวอร์ ให้ทำตามขั้นตอนต่อไปนี้

  1. เข้ารหัสพารามิเตอร์ไบนารีของข้อมูลเข้าสู่ระบบเป็น Base64URL เพื่อให้ส่งไปยังเซิร์ฟเวอร์เป็นสตริงได้

public/client.js

// TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;

// The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
if (cred.authenticatorAttachment) {
  credential.authenticatorAttachment = cred.authenticatorAttachment;
}

// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const attestationObject = base64url.encode(cred.response.attestationObject);

// Obtain transports.
const transports = cred.response.getTransports ? cred.response.getTransports() : [];

credential.response = {
  clientDataJSON,
  attestationObject,
  transports
};
  1. ในบรรทัดถัดไป ให้ส่งออบเจ็กต์ไปยังเซิร์ฟเวอร์

public/client.js

return await _fetch('/auth/registerResponse', credential);

เมื่อเรียกใช้โปรแกรม เซิร์ฟเวอร์จะแสดง HTTP code 200 ซึ่งบ่งชี้ว่ามีการลงทะเบียนข้อมูลเข้าสู่ระบบแล้ว

ตอนนี้คุณมีฟังก์ชัน registerCredential() ที่สมบูรณ์แล้ว

ตรวจสอบโค้ดโซลูชันสำหรับส่วนนี้

public/client.js

// TODO: Add an ability to create a passkey: Create the registerCredential() function.
export async function registerCredential() {

  // TODO: Add an ability to create a passkey: Obtain the challenge and other options from server endpoint.
  const options = await _fetch('/auth/registerRequest');
  
  // TODO: Add an ability to create a passkey: Create a credential.
  // Base64URL decode some values.

  options.user.id = base64url.decode(options.user.id);
  options.challenge = base64url.decode(options.challenge);

  if (options.excludeCredentials) {
    for (let cred of options.excludeCredentials) {
      cred.id = base64url.decode(cred.id);
    }
  }

  // Use platform authenticator and discoverable credential.
  options.authenticatorSelection = {
    authenticatorAttachment: 'platform',
    requireResidentKey: true
  }

  // Invoke the WebAuthn create() method.
  const cred = await navigator.credentials.create({
    publicKey: options,
  });

  // TODO: Add an ability to create a passkey: Register the credential to the server endpoint.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // The authenticatorAttachment string in the PublicKeyCredential object is a new addition in WebAuthn L3.
  if (cred.authenticatorAttachment) {
    credential.authenticatorAttachment = cred.authenticatorAttachment;
  }

  // Base64URL encode some values.
  const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
  const attestationObject =  
  base64url.encode(cred.response.attestationObject);

  // Obtain transports.
  const transports = cred.response.getTransports ? 
  cred.response.getTransports() : [];

  credential.response = {
    clientDataJSON,
    attestationObject,
    transports
  };

  return await _fetch('/auth/registerResponse', credential);
};

4. สร้าง UI เพื่อลงทะเบียนและจัดการข้อมูลเข้าสู่ระบบของพาสคีย์

เมื่อฟังก์ชัน registerCredential() พร้อมใช้งานแล้ว คุณจะต้องมีปุ่มเพื่อเรียกใช้ฟังก์ชันนี้ นอกจากนี้ คุณต้องแสดงรายการพาสคีย์ที่ลงทะเบียนไว้ด้วย

พาสคีย์ที่ลงทะเบียนซึ่งแสดงในหน้า /home

เพิ่ม HTML ของตัวยึดตำแหน่ง

  1. ใน Glitch ให้ไปที่ไฟล์ views/home.html
  2. หลังจากความคิดเห็นที่เกี่ยวข้อง ให้เพิ่มตัวยึดตำแหน่ง UI ที่แสดงปุ่มเพื่อลงทะเบียนพาสคีย์และรายการพาสคีย์

views/home.html

​​<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
  <h3 class="mdc-typography mdc-typography--headline6"> Your registered 
  passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>

องค์ประกอบ div#list เป็นตัวยึดตำแหน่งสำหรับรายการ

ตรวจสอบว่าอุปกรณ์รองรับพาสคีย์หรือไม่

หากต้องการแสดงเฉพาะตัวเลือกในการสร้างพาสคีย์แก่ผู้ใช้ที่มีอุปกรณ์ที่รองรับพาสคีย์ คุณต้องตรวจสอบก่อนว่า WebAuthn พร้อมใช้งานหรือไม่ หากเป็นเช่นนั้น คุณจะต้องนำคลาส hidden ออกเพื่อแสดงปุ่มสร้างพาสคีย์

หากต้องการตรวจสอบว่าสภาพแวดล้อมรองรับพาสคีย์หรือไม่ ให้ทำตามขั้นตอนต่อไปนี้

  1. ที่ส่วนท้ายของviews/home.htmlไฟล์หลังจากความคิดเห็นที่เกี่ยวข้อง ให้เขียนเงื่อนไขที่จะดำเนินการหาก window.PublicKeyCredential, PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable และ PublicKeyCredential.isConditionalMediationAvailable เป็น true

views/home.html

// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');
// Feature detections
if (window.PublicKeyCredential &&
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  1. ในส่วนเนื้อหาของเงื่อนไข ให้ตรวจสอบว่าอุปกรณ์สร้างพาสคีย์ได้หรือไม่ จากนั้นตรวจสอบว่าระบบแนะนำพาสคีย์ในการป้อนข้อความอัตโนมัติในแบบฟอร์มได้หรือไม่

views/home.html

try {
  const results = await Promise.all([

    // Is platform authenticator available in this browser?
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),

    // Is conditional UI available in this browser?
    PublicKeyCredential.isConditionalMediationAvailable()
  ]);
  1. หากเป็นไปตามเงื่อนไขทั้งหมด ให้แสดงปุ่มเพื่อสร้างพาสคีย์ ไม่เช่นนั้น ให้แสดงข้อความเตือน

views/home.html

    if (results.every(r => r === true)) {

      // If conditional UI is available, reveal the Create a passkey button.
      createPasskey.classList.remove('hidden');
    } else {

      // If conditional UI isn't available, show a message.
      $('#message').innerText = 'This device does not support passkeys.';
    }
  } catch (e) {
    console.error(e);
  }
} else {

  // If WebAuthn isn't available, show a message.
  $('#message').innerText = 'This device does not support passkeys.';
}

แสดงพาสคีย์ที่ลงทะเบียนในรายการ

  1. กำหนดrenderCredentials()ฟังก์ชันที่ดึงพาสคีย์ที่ลงทะเบียนจากเซิร์ฟเวอร์และแสดงในรายการ โชคดีที่คุณมี/auth/getKeysปลายทางเซิร์ฟเวอร์อยู่แล้วเพื่อดึงข้อมูลพาสคีย์ที่ลงทะเบียนไว้สำหรับผู้ใช้ที่ลงชื่อเข้าใช้

views/home.html

// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
  const res = await _fetch('/auth/getKeys');
  const list = $('#list');
  const creds = html`${res.length > 0 ? html`
    <mwc-list>
      ${res.map(cred => html`
        <mwc-list-item>
          <div class="list-item">
            <div class="entity-name">
              <span>${cred.name || 'Unnamed' }</span>
          </div>
          <div class="buttons">
            <mwc-icon-button data-cred-id="${cred.id}"  
            data-name="${cred.name || 'Unnamed' }" @click="${rename}"  
            icon="edit"></mwc-icon-button>
            <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" 
            icon="delete"></mwc-icon-button>
          </div>
         </div>
      </mwc-list-item>`)}
  </mwc-list>` : html`
  <mwc-list>
    <mwc-list-item>No credentials found.</mwc-list-item>
  </mwc-list>`}`;
  render(creds, list);
};
  1. ในบรรทัดถัดไป ให้เรียกใช้ฟังก์ชัน renderCredentials() เพื่อแสดงพาสคีย์ที่ลงทะเบียนทันทีที่ผู้ใช้ไปที่หน้า /home เป็นการเริ่มต้น

views/home.html

renderCredentials();

สร้างและลงทะเบียนพาสคีย์

หากต้องการสร้างและลงทะเบียนพาสคีย์ คุณต้องเรียกใช้ฟังก์ชัน registerCredential() ที่คุณใช้งานก่อนหน้านี้

หากต้องการเรียกใช้ฟังก์ชัน registerCredential() เมื่อคลิกปุ่มสร้างพาสคีย์ ให้ทำตามขั้นตอนต่อไปนี้

  1. ในไฟล์ หลังจาก HTML ตัวยึดตำแหน่ง ให้ค้นหาสถานะ import ต่อไปนี้

views/home.html

import { 
  $, 
  _fetch, 
  loading, 
  updateCredential, 
  unregisterCredential, 
} from '/client.js';
  1. ที่ส่วนท้ายของเนื้อหาimportคำสั่งregisterCredential() ให้เพิ่มฟังก์ชัน

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey.
import {
  $,
  _fetch,
  loading,
  updateCredential,
  unregisterCredential,
  registerCredential
} from '/client.js';
  1. ที่ส่วนท้ายของไฟล์หลังจากความคิดเห็นที่เกี่ยวข้อง ให้กำหนดregister()ฟังก์ชันที่เรียกใช้registerCredential()ฟังก์ชันและ UI การโหลด และเรียกใช้renderCredentials()หลังจากลงทะเบียน ซึ่งจะอธิบายว่าเบราว์เซอร์สร้างพาสคีย์และแสดงข้อความแสดงข้อผิดพลาดเมื่อมีสิ่งผิดปกติเกิดขึ้น

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
  try {

    // Start the loading UI.
    loading.start();

    // Start creating a passkey.
    await registerCredential();

    // Stop the loading UI.
    loading.stop();

    // Render the updated passkey list.
    renderCredentials();
  1. ในส่วนเนื้อหาของregister()ฟังก์ชัน ให้ดักจับข้อยกเว้น เมธอด navigator.credentials.create() จะแสดงข้อผิดพลาด InvalidStateError เมื่อมีพาสคีย์ในอุปกรณ์อยู่แล้ว โดยจะตรวจสอบด้วยอาร์เรย์ excludeCredentials ในกรณีนี้ คุณจะแสดงข้อความที่เกี่ยวข้องต่อผู้ใช้ นอกจากนี้ ยังแสดงข้อผิดพลาด NotAllowedError เมื่อผู้ใช้ยกเลิกกล่องโต้ตอบการตรวจสอบสิทธิ์ ในกรณีนี้ คุณไม่จำเป็นต้องสนใจ

views/home.html

  } catch (e) {

    // Stop the loading UI.
    loading.stop();

    // An InvalidStateError indicates that a passkey already exists on the device.
    if (e.name === 'InvalidStateError') {
      alert('A passkey already exists for this device.');

    // A NotAllowedError indicates that the user canceled the operation.
    } else if (e.name === 'NotAllowedError') {
      Return;

    // Show other errors in an alert.
    } else {
      alert(e.message);
      console.error(e);
    }
  }
};
  1. ในบรรทัดหลังฟังก์ชัน register() ให้แนบฟังก์ชัน register() กับเหตุการณ์ click สำหรับปุ่มสร้างพาสคีย์

views/home.html

createPasskey.addEventListener('click', register);

ตรวจสอบโค้ดโซลูชันสำหรับส่วนนี้

views/home.html

​​<!-- TODO: Add an ability to create a passkey: Add placeholder HTML. -->
<section>
  <h3 class="mdc-typography mdc-typography--headline6"> Your registered  
  passkeys:</h3>
  <div id="list"></div>
</section>
<p id="message" class="instructions"></p>
<mwc-button id="create-passkey" class="hidden" icon="fingerprint" raised>Create a passkey</mwc-button>

views/home.html

// TODO: Add an ability to create a passkey: Create and register a passkey.
import { 
  $, 
  _fetch, 
  loading, 
  updateCredential, 
  unregisterCredential, 
  registerCredential 
} from '/client.js';

views/home.html

// TODO: Add an ability to create a passkey: Check for passkey support.
const createPasskey = $('#create-passkey');

// Feature detections
if (window.PublicKeyCredential &&
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  try {
    const results = await Promise.all([

      // Is platform authenticator available in this browser?
      PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),

      // Is conditional UI available in this browser?
      PublicKeyCredential.isConditionalMediationAvailable()
    ]);
    if (results.every(r => r === true)) {

      // If conditional UI is available, reveal the Create a passkey button.
      createPasskey.classList.remove('hidden');
    } else {

      // If conditional UI isn't available, show a message.
      $('#message').innerText = 'This device does not support passkeys.';
    }
  } catch (e) {
    console.error(e);
  }
} else {

  // If WebAuthn isn't available, show a message.
  $('#message').innerText = 'This device does not support passkeys.';
}

// TODO: Add an ability to create a passkey: Render registered passkeys in a list.
async function renderCredentials() {
  const res = await _fetch('/auth/getKeys');
  const list = $('#list');
  const creds = html`${res.length > 0 ? html`
  <mwc-list>
    ${res.map(cred => html`
      <mwc-list-item>
        <div class="list-item">
          <div class="entity-name">
            <span>${cred.name || 'Unnamed' }</span>
          </div>
          <div class="buttons">
            <mwc-icon-button data-cred-id="${cred.id}" data-name="${cred.name || 'Unnamed' }" @click="${rename}" icon="edit"></mwc-icon-button>
            <mwc-icon-button data-cred-id="${cred.id}" @click="${remove}" icon="delete"></mwc-icon-button>
          </div>
        </div>
      </mwc-list-item>`)}
  </mwc-list>` : html`
  <mwc-list>
    <mwc-list-item>No credentials found.</mwc-list-item>
  </mwc-list>`}`;
  render(creds, list);
};

renderCredentials();

// TODO: Add an ability to create a passkey: Create and register a passkey.
async function register() {
  try {

    // Start the loading UI.
    loading.start();

    // Start creating a passkey.
    await registerCredential();

    // Stop the loading UI.
    loading.stop();

    // Render the updated passkey list.
    renderCredentials();
  } catch (e) {

    // Stop the loading UI.
    loading.stop();

    // An InvalidStateError indicates that a passkey already exists on the device.
    if (e.name === 'InvalidStateError') {
      alert('A passkey already exists for this device.');

    // A NotAllowedError indicates that the user canceled the operation.
    } else if (e.name === 'NotAllowedError') {
      Return;

    // Show other errors in an alert.
    } else {
      alert(e.message);
      console.error(e);
    }
  }
};

createPasskey.addEventListener('click', register);

ลองใช้

หากทำตามขั้นตอนทั้งหมดจนถึงตอนนี้ คุณได้ติดตั้งใช้งานความสามารถในการสร้าง ลงทะเบียน และแสดงพาสคีย์ในเว็บไซต์แล้ว

หากต้องการลองใช้ ให้ทำตามขั้นตอนต่อไปนี้

  1. ในแท็บแสดงตัวอย่าง ให้ลงชื่อเข้าใช้ด้วยชื่อผู้ใช้และรหัสผ่านแบบสุ่ม
  2. คลิกสร้างพาสคีย์
  3. ยืนยันตัวตนด้วยการล็อกหน้าจอของอุปกรณ์
  4. ยืนยันว่ามีการลงทะเบียนพาสคีย์และแสดงในส่วนพาสคีย์ที่ลงทะเบียนของหน้าเว็บ

พาสคีย์ที่ลงทะเบียนซึ่งแสดงในหน้า /home

เปลี่ยนชื่อและนำพาสคีย์ที่ลงทะเบียนออก

คุณควรเปลี่ยนชื่อหรือลบรหัสผ่านที่บันทึกไว้ในรายการได้ คุณสามารถตรวจสอบวิธีการทำงานในโค้ดได้เนื่องจากมาพร้อมกับโค้ดแล็บ

ใน Chrome คุณสามารถนำพาสคีย์ที่ลงทะเบียนไว้ออกจาก chrome://settings/passkeys บนเดสก์ท็อป หรือจากเครื่องมือจัดการรหัสผ่านในการตั้งค่าบน Android ได้

ดูข้อมูลเกี่ยวกับวิธีเปลี่ยนชื่อและนำพาสคีย์ที่ลงทะเบียนออกในแพลตฟอร์มอื่นๆ ได้ที่หน้าการสนับสนุนที่เกี่ยวข้องของแพลตฟอร์มเหล่านั้น

5. เพิ่มความสามารถในการตรวจสอบสิทธิ์ด้วยพาสคีย์

ตอนนี้ผู้ใช้สามารถสร้างและลงทะเบียนพาสคีย์ได้แล้ว และพร้อมที่จะใช้พาสคีย์เป็นวิธีตรวจสอบสิทธิ์ในเว็บไซต์ของคุณอย่างปลอดภัย ตอนนี้คุณต้องเพิ่มความสามารถในการตรวจสอบสิทธิ์ด้วยพาสคีย์ลงในเว็บไซต์

สร้างฟังก์ชัน authenticate()

  • ในไฟล์ public/client.js หลังจากความคิดเห็นที่เกี่ยวข้อง ให้สร้างฟังก์ชันชื่อ authenticate() ที่จะยืนยันผู้ใช้ในเครื่อง แล้วจึงยืนยันกับเซิร์ฟเวอร์

public/client.js

// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {

  // TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.

  // TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.

  // TODO: Add an ability to authenticate with a passkey: Verify the credential.

};

รับคำท้าและตัวเลือกอื่นๆ จากปลายทางของเซิร์ฟเวอร์

ก่อนขอให้ผู้ใช้ตรวจสอบสิทธิ์ คุณต้องขอพารามิเตอร์เพื่อส่งใน WebAuthn จากเซิร์ฟเวอร์ รวมถึงการท้าทาย

  • ในเนื้อหาของฟังก์ชัน authenticate() หลังจากความคิดเห็นที่เกี่ยวข้อง ให้เรียกใช้ฟังก์ชัน _fetch() เพื่อส่งคำขอ POST ไปยังเซิร์ฟเวอร์

public/client.js

// TODO: Add an ability to authenticate with a passkey: Obtain the challenge and other options from the server endpoint.
const options = await _fetch('/auth/signinRequest');

เซิร์ฟเวอร์ของโค้ดแล็บนี้ออกแบบมาเพื่อแสดงผล JSON ที่คล้ายกับพจนานุกรม PublicKeyCredentialRequestOptions ที่ส่งไปยัง API ของ WebAuthn navigator.credentials.get() ให้มากที่สุด ข้อมูลโค้ดต่อไปนี้มีตัวเลือกตัวอย่างที่คุณควรได้รับ

{
  "challenge": *****,
  "rpId": "passkeys-codelab.glitch.me",
  "allowCredentials": []
}

ตารางต่อไปนี้ไม่ได้ครอบคลุมทั้งหมด แต่มีพารามิเตอร์ที่สำคัญในPublicKeyCredentialRequestOptionsพจนานุกรม

พารามิเตอร์

คำอธิบาย

challenge

คำท้าที่เซิร์ฟเวอร์สร้างขึ้นในออบเจ็กต์ ArrayBuffer ซึ่งจำเป็นต้องทำเพื่อป้องกันการโจมตีแบบส่งซ้ำ อย่ารับคำท้าเดียวกันในคำตอบ 2 ครั้ง ถือว่าเป็นโทเค็น CSRF

rpId

RP ID คือโดเมน เว็บไซต์สามารถระบุโดเมนหรือคำต่อท้ายที่จดทะเบียนได้ ค่านี้ต้องตรงกับrp.idพารามิเตอร์ที่ใช้เมื่อสร้างพาสคีย์

allowCredentials

ระบบใช้พร็อพเพอร์ตี้นี้เพื่อค้นหาเครื่องมือตรวจสอบสิทธิ์ที่มีสิทธิ์สำหรับการตรวจสอบสิทธิ์นี้ ส่งอาร์เรย์ว่างหรือปล่อยให้ไม่ได้ระบุเพื่อให้เบราว์เซอร์แสดงตัวเลือกบัญชี

userVerification

ตั้งค่าเป็น "preferred" หรือละเว้นไว้เนื่องจากเป็นค่าเริ่มต้น ซึ่งระบุว่าการยืนยันตัวตนของผู้ใช้โดยใช้การล็อกหน้าจอของอุปกรณ์เป็น "required", "preferred" หรือ "discouraged" การตั้งค่าเป็นค่า "preferred" จะขอการยืนยันผู้ใช้เมื่ออุปกรณ์ทำได้

ยืนยันตัวตนผู้ใช้ในเครื่องและรับข้อมูลเข้าสู่ระบบ

  1. ในส่วนเนื้อหาของฟังก์ชัน authenticate() หลังจากความคิดเห็นที่เกี่ยวข้อง ให้แปลงพารามิเตอร์ challenge กลับเป็นไบนารี

public/client.js

// TODO: Add an ability to authenticate with a passkey: Locally verify the user and get a credential.
// Base64URL decode the challenge.
options.challenge = base64url.decode(options.challenge);
  1. ส่งอาร์เรย์ว่างไปยังพารามิเตอร์ allowCredentials เพื่อเปิดตัวเลือกบัญชีเมื่อผู้ใช้ตรวจสอบสิทธิ์

public/client.js

// An empty allowCredentials array invokes an account selector by discoverable credentials.
options.allowCredentials = [];

ตัวเลือกบัญชีจะใช้ข้อมูลของผู้ใช้ที่จัดเก็บไว้กับพาสคีย์

  1. เรียกใช้เมธอด navigator.credentials.get() พร้อมกับตัวเลือก mediation: 'conditional' ดังนี้

public/client.js

// Invoke the WebAuthn get() method.
const cred = await navigator.credentials.get({
  publicKey: options,

  // Request a conditional UI.
  mediation: 'conditional'
});

ตัวเลือกนี้จะสั่งให้เบราว์เซอร์แนะนำพาสคีย์แบบมีเงื่อนไขเป็นส่วนหนึ่งของการป้อนข้อความอัตโนมัติในแบบฟอร์ม

ยืนยันข้อมูลเข้าสู่ระบบ

หลังจากที่ผู้ใช้ยืนยันตัวตนในเครื่องแล้ว คุณควรได้รับออบเจ็กต์ข้อมูลเข้าสู่ระบบที่มีลายเซ็นซึ่งคุณสามารถยืนยันในเซิร์ฟเวอร์ได้

ข้อมูลโค้ดต่อไปนี้มีตัวอย่างออบเจ็กต์ PublicKeyCredential

{
  "id": *****,
  "rawId": *****,
  "type": "public-key",
  "response": {
    "clientDataJSON": *****,
    "authenticatorData": *****,
    "signature": *****,
    "userHandle": *****
  },
  authenticatorAttachment: "platform"
}

ตารางต่อไปนี้ไม่ได้ครอบคลุมทั้งหมด แต่มีพารามิเตอร์ที่สำคัญในออบเจ็กต์ PublicKeyCredential

พารามิเตอร์

คำอธิบาย

id

รหัสที่เข้ารหัส Base64URL ของข้อมูลเข้าสู่ระบบพาสคีย์ที่ได้รับการตรวจสอบสิทธิ์

rawId

ArrayBuffer เวอร์ชันออบเจ็กต์ของรหัสข้อมูลเข้าสู่ระบบ

response.clientDataJSON

ออบเจ็กต์ ArrayBuffer ของข้อมูลลูกค้า ฟิลด์นี้มีข้อมูล เช่น คำท้าและต้นทางที่เซิร์ฟเวอร์ RP ต้องยืนยัน

response.authenticatorData

ออบเจ็กต์ ArrayBuffer ของข้อมูลเครื่องมือตรวจสอบสิทธิ์ ฟิลด์นี้มีข้อมูล เช่น รหัส RP

response.signature

ออบเจ็กต์ ArrayBuffer ของลายเซ็น ค่านี้เป็นหัวใจสำคัญของข้อมูลเข้าสู่ระบบและต้องได้รับการยืนยันในเซิร์ฟเวอร์

response.userHandle

ArrayBuffer ออบเจ็กต์ที่มีรหัสผู้ใช้ที่ตั้งค่าไว้ตอนสร้าง ค่านี้สามารถใช้แทนรหัสข้อมูลเข้าสู่ระบบได้หากเซิร์ฟเวอร์ต้องการเลือกค่ารหัสที่จะใช้ หรือหากแบ็กเอนด์ต้องการหลีกเลี่ยงการสร้างดัชนีในรหัสข้อมูลเข้าสู่ระบบ

authenticatorAttachment

แสดงผล "platform" สตริงเมื่อข้อมูลเข้าสู่ระบบนี้มาจากอุปกรณ์ในเครื่อง มิฉะนั้นจะแสดงสตริง "cross-platform" โดยเฉพาะเมื่อผู้ใช้ใช้โทรศัพท์เพื่อลงชื่อเข้าใช้ หากผู้ใช้ต้องใช้โทรศัพท์เพื่อลงชื่อเข้าใช้ ให้แจ้งให้ผู้ใช้สร้างพาสคีย์ในอุปกรณ์เครื่องนั้น

หากต้องการส่งออบเจ็กต์ข้อมูลเข้าสู่ระบบไปยังเซิร์ฟเวอร์ ให้ทำตามขั้นตอนต่อไปนี้

  1. ในส่วนเนื้อหาของฟังก์ชัน authenticate() หลังจากความคิดเห็นที่เกี่ยวข้อง ให้เข้ารหัสพารามิเตอร์ไบนารีของข้อมูลเข้าสู่ระบบเพื่อให้ส่งไปยังเซิร์ฟเวอร์เป็นสตริงได้

public/client.js

// TODO: Add an ability to authenticate with a passkey: Verify the credential.
const credential = {};
credential.id = cred.id;
credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
credential.type = cred.type;

// Base64URL encode some values.
const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
const authenticatorData = base64url.encode(cred.response.authenticatorData);
const signature = base64url.encode(cred.response.signature);
const userHandle = base64url.encode(cred.response.userHandle);

credential.response = {
  clientDataJSON,
  authenticatorData,
  signature,
  userHandle,
};
  1. ส่งออบเจ็กต์ไปยังเซิร์ฟเวอร์

public/client.js

return await _fetch(`/auth/signinResponse`, credential);

เมื่อคุณเรียกใช้โปรแกรม เซิร์ฟเวอร์จะแสดง HTTP code 200 ซึ่งบ่งบอกว่าระบบได้ยืนยันข้อมูลเข้าสู่ระบบแล้ว

ตอนนี้คุณมีฟังก์ชัน authentication() เต็มรูปแบบแล้ว

ตรวจสอบโค้ดโซลูชันสำหรับส่วนนี้

public/client.js

// TODO: Add an ability to authenticate with a passkey: Create the authenticate() function.
export async function authenticate() {

  // TODO: Add an ability to authenticate with a passkey: Obtain the 
  challenge and other options from the server endpoint.
  const options = await _fetch('/auth/signinRequest');

  // TODO: Add an ability to authenticate with a passkey: Locally verify 
  the user and get a credential.
  // Base64URL decode the challenge.
  options.challenge = base64url.decode(options.challenge);

  // The empty allowCredentials array invokes an account selector 
  by discoverable credentials.
  options.allowCredentials = [];

  // Invoke the WebAuthn get() function.
  const cred = await navigator.credentials.get({
    publicKey: options,

    // Request a conditional UI.
    mediation: 'conditional'
  });

  // TODO: Add an ability to authenticate with a passkey: Verify the credential.
  const credential = {};
  credential.id = cred.id;
  credential.rawId = cred.id; // Pass a Base64URL encoded ID string.
  credential.type = cred.type;

  // Base64URL encode some values.
  const clientDataJSON = base64url.encode(cred.response.clientDataJSON);
  const authenticatorData = 
  base64url.encode(cred.response.authenticatorData);
  const signature = base64url.encode(cred.response.signature);
  const userHandle = base64url.encode(cred.response.userHandle);

  credential.response = {
    clientDataJSON,
    authenticatorData,
    signature,
    userHandle,
  };

  return await _fetch(`/auth/signinResponse`, credential);
};

6. เพิ่มพาสคีย์ลงในข้อมูลที่ป้อนอัตโนมัติของเบราว์เซอร์

เมื่อผู้ใช้กลับมา คุณต้องการให้ผู้ใช้ลงชื่อเข้าใช้ได้ง่ายและปลอดภัยที่สุด หากเพิ่มปุ่มลงชื่อเข้าใช้ด้วยพาสคีย์ในหน้าเข้าสู่ระบบ ผู้ใช้จะกดปุ่ม เลือกพาสคีย์ในตัวเลือกบัญชีของเบราว์เซอร์ และใช้การล็อกหน้าจอเพื่อยืนยันตัวตนได้

อย่างไรก็ตาม การเปลี่ยนจากรหัสผ่านเป็นพาสคีย์ไม่ได้เกิดขึ้นกับผู้ใช้ทุกคนในคราวเดียว ซึ่งหมายความว่าคุณจะกำจัดรหัสผ่านไม่ได้จนกว่าผู้ใช้ทั้งหมดจะเปลี่ยนไปใช้พาสคีย์ ดังนั้นคุณจึงต้องปล่อยให้แบบฟอร์มลงชื่อเข้าใช้ด้วยรหัสผ่านไว้จนกว่าจะถึงตอนนั้น อย่างไรก็ตาม หากคุณปล่อยให้มีทั้งแบบฟอร์มรหัสผ่านและปุ่มพาสคีย์ ผู้ใช้จะต้องเลือกระหว่างรหัสผ่านกับพาสคีย์เพื่อลงชื่อเข้าใช้ ซึ่งเป็นตัวเลือกที่ไม่จำเป็น คุณควรมีกระบวนการลงชื่อเข้าใช้ที่ไม่ซับซ้อน

UI แบบมีเงื่อนไขจึงเข้ามามีบทบาทในจุดนี้ UI แบบมีเงื่อนไขเป็นฟีเจอร์ WebAuthn ที่คุณสามารถสร้างช่องป้อนข้อมูลแบบฟอร์มเพื่อแนะนำพาสคีย์เป็นส่วนหนึ่งของรายการกรอกข้อมูลอัตโนมัติได้ นอกเหนือจากรหัสผ่าน หากผู้ใช้แตะพาสคีย์ในคำแนะนำการป้อนข้อความอัตโนมัติ ระบบจะขอให้ผู้ใช้ใช้การล็อกหน้าจอของอุปกรณ์เพื่อยืนยันตัวตนในเครื่อง ซึ่งเป็นประสบการณ์ของผู้ใช้ที่ราบรื่นเนื่องจากการดำเนินการของผู้ใช้แทบจะเหมือนกับการลงชื่อเข้าใช้ด้วยรหัสผ่าน

พาสคีย์ที่แนะนำเป็นส่วนหนึ่งของการป้อนข้อความอัตโนมัติในแบบฟอร์ม

เปิดใช้ UI แบบมีเงื่อนไข

หากต้องการเปิดใช้ UI แบบมีเงื่อนไข สิ่งที่คุณต้องทำคือเพิ่มโทเค็น webauthn ในแอตทริบิวต์ autocomplete ของช่องป้อนข้อมูล เมื่อตั้งค่าโทเค็นแล้ว คุณจะเรียกใช้เมธอด navigator.credentials.get() ด้วยสตริง mediation: 'conditional' เพื่อทริกเกอร์ UI การล็อกหน้าจอแบบมีเงื่อนไขได้

  • หากต้องการเปิดใช้ UI แบบมีเงื่อนไข ให้แทนที่ช่องป้อนชื่อผู้ใช้ที่มีอยู่ด้วย HTML ต่อไปนี้หลังจากความคิดเห็นที่เกี่ยวข้องในไฟล์ view/index.html

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
  type="text"
  id="username"
  class="mdc-text-field__input"
  aria-labelledby="username-label"
  name="username"
  autocomplete="username webauthn"
  autofocus />

ตรวจหาฟีเจอร์ เรียกใช้ WebAuthn และเปิดใช้ UI แบบมีเงื่อนไข

  1. ในไฟล์ view/index.html หลังความคิดเห็นที่เกี่ยวข้อง ให้แทนที่คำสั่ง import ที่มีอยู่ด้วยโค้ดต่อไปนี้

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import {
  $,
  _fetch,
  loading,
  authenticate 
} from "/client.js";

โค้ดนี้จะนำเข้าฟังก์ชัน authenticate() ที่คุณใช้งานก่อนหน้านี้

  1. ตรวจสอบว่าออบเจ็กต์ window.PulicKeyCredential พร้อมใช้งานและเมธอด PublicKeyCredential.isConditionalMediationAvailable() แสดงผลค่า true จากนั้นเรียกใช้ฟังก์ชัน authenticate() ดังนี้

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
if (
  window.PublicKeyCredential &&
  PublicKeyCredential.isConditionalMediationAvailable
) {
  try {

    // Is conditional UI available in this browser?
    const cma =
      await PublicKeyCredential.isConditionalMediationAvailable();
    if (cma) {

      // If conditional UI is available, invoke the authenticate() function.
      const user = await authenticate();
      if (user) {

        // Proceed only when authentication succeeds.
        $("#username").value = user.username;
        loading.start();
        location.href = "/home";
      } else {
        throw new Error("User not found.");
      }
    }
  } catch (e) {
    loading.stop();

    // A NotAllowedError indicates that the user canceled the operation.
    if (e.name !== "NotAllowedError") {
      console.error(e);
      alert(e.message);
    }
  }
}

ตรวจสอบโค้ดโซลูชันสำหรับส่วนนี้

view/index.html

<!-- TODO: Add passkeys to the browser autofill: Enable conditional UI. -->
<input
  type="text"
  id="username"
  class="mdc-text-field__input"
  aria-labelledby="username-label"
  name="username"
  autocomplete="username webauthn"
  autofocus 
/>

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.
import { 
  $, 
  _fetch, 
  loading, 
  authenticate 
} from '/client.js';

view/index.html

// TODO: Add passkeys to the browser autofill: Detect features, invoke WebAuthn, and enable a conditional UI.        
// Is WebAuthn avaiable in this browser?
if (window.PublicKeyCredential &&
    PublicKeyCredential.isConditionalMediationAvailable) {
  try {

    // Is a conditional UI available in this browser?
    const cma= await PublicKeyCredential.isConditionalMediationAvailable();
    if (cma) {

      // If a conditional UI is available, invoke the authenticate() function.
      const user = await authenticate();
      if (user) {

        // Proceed only when authentication succeeds.
        $('#username').value = user.username;
        loading.start();
        location.href = '/home';
      } else {
        throw new Error('User not found.');
      }
    }
  } catch (e) {
    loading.stop();

    // A NotAllowedError indicates that the user canceled the operation.
    if (e.name !== 'NotAllowedError') {
      console.error(e);
      alert(e.message);
    }
  }
}

ลองใช้

คุณได้ติดตั้งใช้งานการสร้าง การลงทะเบียน การแสดง และการตรวจสอบสิทธิ์ของพาสคีย์ในเว็บไซต์

หากต้องการลองใช้ ให้ทำตามขั้นตอนต่อไปนี้

  1. ไปที่แท็บตัวอย่าง
  2. ลงชื่อออกหากจำเป็น
  3. คลิกกล่องข้อความชื่อผู้ใช้ กล่องโต้ตอบจะปรากฏขึ้น
  4. เลือกบัญชีที่ต้องการใช้ลงชื่อเข้าใช้
  5. ยืนยันตัวตนด้วยการล็อกหน้าจอของอุปกรณ์ ระบบจะเปลี่ยนเส้นทางคุณไปยังหน้า /home และลงชื่อเข้าใช้

กล่องโต้ตอบที่แจ้งให้คุณยืนยันตัวตนด้วยรหัสผ่านหรือพาสคีย์ที่บันทึกไว้

7. ยินดีด้วย

คุณทำ Codelab นี้เสร็จแล้ว หากมีคำถาม โปรดถามในรายชื่ออีเมล FIDO-DEV หรือใน StackOverflow โดยใช้แท็ก passkey

ดูข้อมูลเพิ่มเติม