รักษาเว็บไซต์ของคุณให้ปลอดภัยด้วยการตรวจสอบสิทธิ์แบบ 2 ปัจจัยด้วยคีย์ความปลอดภัย (WebAuthn)

1. สิ่งที่คุณจะสร้าง

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

จากนั้นเพิ่มการรองรับการตรวจสอบสิทธิ์แบบ 2 ปัจจัยผ่านคีย์ความปลอดภัยโดยอิงตาม WebAuthn ในการทําเช่นนี้ คุณต้องดําเนินการต่อไปนี้

  • วิธีการลงทะเบียนข้อมูลเข้าสู่ระบบ WebAuthn สําหรับผู้ใช้
  • ขั้นตอนการตรวจสอบสิทธิ์แบบ 2 ปัจจัยที่ผู้ใช้ถูกถามถึงปัจจัยที่ 2 คือข้อมูลรับรอง WebAuthn (หากลงทะเบียนไว้)
  • อินเทอร์เฟซการจัดการข้อมูลเข้าสู่ระบบ: รายการข้อมูลเข้าสู่ระบบที่อนุญาตให้ผู้ใช้เปลี่ยนชื่อและลบข้อมูลเข้าสู่ระบบได้

16ce77744061c5f7.png

ลองดูเว็บแอปที่ลองใช้แล้วลองใช้

2. เกี่ยวกับ WebAuthn

ข้อมูลเบื้องต้นเกี่ยวกับ WebAuthn

ทําไมจึงต้องใช้ WebAuthn

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

Web Authentication API หรือ WebAuthn เป็นโปรโตคอลแบบป้องกันฟิชชิงแบบมาตรฐานที่เว็บแอปพลิเคชันใดก็ได้ใช้

วิธีการทำงาน

แหล่งที่มา: webauthn.guide

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

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

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

ข้อดี

WebAuthn มีประโยชน์หลัก 2 ประการดังนี้

  • ไม่มีข้อมูลลับที่ใช้ร่วมกัน: เซิร์ฟเวอร์ไม่มีข้อมูลลับ ซึ่งช่วยให้ฐานข้อมูลนี้ดึงดูดแฮ็กเกอร์ได้น้อยลงเนื่องจากคีย์สาธารณะไม่มีประโยชน์สําหรับพวกเขา
  • ข้อมูลเข้าสู่ระบบที่กําหนดขอบเขต: ข้อมูลเข้าสู่ระบบที่ลงทะเบียนสําหรับ site.example จะใช้ใน evil-site.example ไม่ได้ วิธีนี้จะทําให้ WebAuthn กันน้ําได้

Use Case

Use Case อย่างหนึ่งสําหรับ WebAuthn คือการตรวจสอบสิทธิ์แบบ 2 ปัจจัยด้วยคีย์ความปลอดภัย ซึ่งอาจเกี่ยวข้องกับเว็บแอปพลิเคชันจากองค์กรโดยเฉพาะ

การสนับสนุนเบราว์เซอร์

เขียนโดย W3C และ FIDO และได้รับการสนับสนุนจาก Google, Mozilla, Microsoft, Yubico และอื่นๆ

อภิธานศัพท์

  • Authenticator: นิติบุคคลซอฟต์แวร์หรือฮาร์ดแวร์ที่สามารถลงทะเบียนผู้ใช้ ตลอดจนยืนยันการครอบครองข้อมูลเข้าสู่ระบบที่จดทะเบียนในภายหลังได้ ตัวตรวจสอบสิทธิ์มี 2 ประเภทดังนี้
  • โรมมิ่งที่มีการตรวจสอบสิทธิ์: โปรแกรมตรวจสอบสิทธิ์ซึ่งใช้กับอุปกรณ์ใดก็ได้ที่ผู้ใช้พยายามลงชื่อเข้าใช้ เช่น คีย์ความปลอดภัย USB สมาร์ทโฟน
  • Authenticator ของแพลตฟอร์ม: Authenticator ที่มีอยู่ในอุปกรณ์ของผู้ใช้ ตัวอย่าง: Touch ID ของ Apple
  • ข้อมูลเข้าสู่ระบบ: คู่คีย์ส่วนตัว-สาธารณะ
  • บุคคลอื่น: เว็บไซต์ (สําหรับ) เว็บไซต์ที่พยายามตรวจสอบสิทธิ์ผู้ใช้
  • เซิร์ฟเวอร์ FIDO: เซิร์ฟเวอร์ที่ใช้สําหรับการตรวจสอบสิทธิ์ FIDO คือชุดโปรโตคอลที่พัฒนาโดยพาร์ทเนอร์ของ FIDO หนึ่งในโปรโตคอลเหล่านี้คือ WebAuthn

ในเวิร์กช็อปนี้ เราจะใช้ Authenticator สําหรับการโรมมิ่ง

3. ข้อควรทราบก่อนที่จะเริ่มต้น

สิ่งที่ต้องมี

Codelab นี้เสร็จสมบูรณ์ คุณจะต้องมีสิ่งต่อไปนี้

  • ความเข้าใจเบื้องต้นเกี่ยวกับ WebAuthn
  • ความรู้พื้นฐานเกี่ยวกับ JavaScript และ HTML
  • เบราว์เซอร์เวอร์ชันล่าสุดที่รองรับ WebAuthn
  • คีย์ความปลอดภัยที่สอดคล้องกับ U2F

คุณสามารถใช้รายการใดรายการหนึ่งต่อไปนี้เป็นคีย์ความปลอดภัยได้

  • โทรศัพท์ Android ที่ใช้ Android>=7 (Nougat) ซึ่งเรียกใช้ Chrome ในกรณีนี้ คุณจะต้องใช้เครื่อง Windows, macOS หรือ Chrome OS ที่มีบลูทูธที่ใช้งานได้ด้วย
  • คีย์ USB เช่น YubiKey

6539dc7ffec2538c.png

แหล่งที่มา: https://www.yubico.com/products/security-key/

dd56e2cfe0f7ced2.png

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

คุณจะได้เรียนรู้

  • วิธีลงทะเบียนและใช้คีย์ความปลอดภัยเป็นปัจจัยที่ 2 สําหรับการตรวจสอบสิทธิ์ WebAuthn
  • วิธีทําให้กระบวนการนี้เป็นมิตรกับผู้ใช้

คุณจะไม่เรียนรู้อีก ❌

  • วิธีสร้างเซิร์ฟเวอร์ FIDO ซึ่งเป็นเซิร์ฟเวอร์ที่ใช้สําหรับการตรวจสอบสิทธิ์ การดําเนินการนี้เป็นเรื่องปกติ เนื่องจากโดยทั่วไปในฐานะเว็บแอปพลิเคชันหรือนักพัฒนาเว็บไซต์ คุณจะต้องใช้การติดตั้งใช้งาน FIDO Server ที่มีอยู่ โปรดตรวจสอบฟังก์ชันและคุณภาพของการใช้งานเซิร์ฟเวอร์ที่คุณไว้วางใจเสมอ ใน Codelab นี้ เซิร์ฟเวอร์ FIDO จะใช้ SimpleWebAuthn ดูตัวเลือกอื่นๆ ที่หน้าอย่างเป็นทางการของ FIDO Alliance สําหรับไลบรารีโอเพนซอร์ส โปรดดู webauthn.io หรือ AwesomeWebAuthn

ข้อจำกัดความรับผิด

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

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

4. ตั้งค่า Authenticator

หากคุณใช้โทรศัพท์ Android เป็น Authenticator

  • ตรวจสอบว่า Chrome เป็นเวอร์ชันล่าสุดทั้งในเดสก์ท็อปและในโทรศัพท์
  • เปิด Chrome ทั้งในเดสก์ท็อปและในโทรศัพท์แล้วลงชื่อเข้าใช้ด้วยโปรไฟล์เดียวกัน⏤ โปรไฟล์ที่ต้องการใช้สําหรับเวิร์กช็อปนี้
  • เปิดการซิงค์สําหรับโปรไฟล์นี้บนเดสก์ท็อปและโทรศัพท์ ใช้ chrome://settings/syncSetup เพื่อทําสิ่งนี้
  • เปิดบลูทูธทั้งบนเดสก์ท็อปและโทรศัพท์
  • ใน Chrome Desktop Desktop ที่ลงชื่อเข้าสู่ระบบด้วยโปรไฟล์เดียวกัน ให้เปิด webauthn.io
  • ป้อนชื่อผู้ใช้ง่ายๆ ปล่อยให้ประเภทเอกสารรับรองและประเภท Authenticator เป็นค่าไม่มีและไม่ระบุ (ค่าเริ่มต้น) คลิกลงทะเบียน

6b49ff0298f5a0af.png

  • หน้าต่างเบราว์เซอร์ควรเปิดขึ้นเพื่อขอให้คุณยืนยันตัวตน เลือกโทรศัพท์ในรายการ

ffebe58ac826eaf2.png 852de328fcd4eb42.png

  • คุณจะได้รับการแจ้งเตือนชื่อยืนยันตัวตนในโทรศัพท์ แล้วแตะแอปนั้น
  • ในโทรศัพท์ ระบบจะถามรหัส PIN ของโทรศัพท์ (หรือโดยการแตะเซ็นเซอร์ลายนิ้วมือ) ป้อนข้อมูล
  • ใน webauthn.io ในเดสก์ท็อปของคุณ ตัวบ่งชี้ "Success" ควรจะปรากฏขึ้น

fc0acf00a4d412fa.png

  • คลิกปุ่มเข้าสู่ระบบบน webauthn.io บนเดสก์ท็อป
  • เช่นเดียวกัน หน้าต่างเบราว์เซอร์ควรเปิดขึ้น ให้เลือกโทรศัพท์ในรายการ
  • แตะการแจ้งเตือนในโทรศัพท์ที่ปรากฏขึ้น แล้วป้อน PIN (หรือแตะเซ็นเซอร์ลายนิ้วมือ)
  • webauthn.io ควรแจ้งว่าคุณลงชื่อเข้าสู่ระบบแล้ว โทรศัพท์ทํางานไม่ถูกต้องเป็นคีย์ความปลอดภัย คุณพร้อมเวิร์กช็อปแล้ว

หากใช้คีย์ความปลอดภัยแบบ USB เป็น Authenticator

  • เปิด webauthn.io ในเดสก์ท็อปของ Chrome
  • ป้อนชื่อผู้ใช้ง่ายๆ ปล่อยให้ประเภทเอกสารรับรองและประเภท Authenticator เป็นค่าไม่มีและไม่ระบุ (ค่าเริ่มต้น) คลิกลงทะเบียน
  • หน้าต่างเบราว์เซอร์ควรเปิดขึ้นเพื่อขอให้คุณยืนยันตัวตน เลือกคีย์ความปลอดภัยแบบ USB ในรายการ

ffebe58ac826eaf2.png 9fe75f04e43da035

  • เสียบคีย์ความปลอดภัยกับเดสก์ท็อป แล้วแตะคีย์นั้น

923d5adb8aa8286c.png

  • ใน webauthn.io ในเดสก์ท็อปของคุณ ตัวบ่งชี้ "Success" ควรจะปรากฏขึ้น

fc0acf00a4d412fa.png

  • คลิกปุ่ม Login ใน webauthn.io บนเดสก์ท็อป
  • หน้าต่างเบราว์เซอร์ควรเปิดอีกครั้ง เลือกคีย์ความปลอดภัย USB ในรายการ
  • แตะแป้น
  • Webauthn.io ควรแจ้งว่าคุณลงชื่อเข้าสู่ระบบแล้ว คีย์ความปลอดภัย USB ทํางานเป็นปกติ คุณพร้อมสําหรับเวิร์กช็อปแล้ว

7e1c0bb19c9f3043.png

5. ตั้งค่า

ใน Codelab นี้ คุณจะใช้ Glitch ซึ่งเป็นตัวแก้ไขโค้ดออนไลน์ที่ทําให้โค้ดใช้งานได้โดยอัตโนมัติในทันที

แยกโค้ดเริ่มต้น

เปิดโปรเจ็กต์เริ่มต้น

คลิกปุ่มรีมิกซ์

การดําเนินการนี้จะสร้างสําเนาของโค้ดเริ่มต้น ตอนนี้คุณมีโค้ดของตนเองสําหรับแก้ไขแล้ว ส้อม (เรียกว่า "remix" ใน Glitch) คือที่ที่คุณจะทํางานทั้งหมดสําหรับ Codelab นี้

cf2b9f552c9809b6.png

สํารวจโค้ดเริ่มต้น

สํารวจโค้ดเริ่มต้นที่คุณเพิ่งดูไปสักระยะหนึ่ง

โปรดทราบว่า libs มีไลบรารีชื่อ auth.js อยู่แล้ว โดยเป็นไลบรารีที่กําหนดเองซึ่งดูแลตรรกะการตรวจสอบสิทธิ์ฝั่งเซิร์ฟเวอร์ โดยใช้ไลบรารี fido เป็นทรัพยากร Dependency

6. ใช้การลงทะเบียนข้อมูลเข้าสู่ระบบ

ใช้การลงทะเบียนข้อมูลเข้าสู่ระบบ

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

เรามาเพิ่มฟังก์ชันที่ทําในโค้ดฝั่งไคลเอ็นต์ก่อนกัน

ใน public/auth.client.js โปรดทราบว่ายังมีฟังก์ชันที่เรียกว่า registerCredential() ซึ่งยังไม่ได้ทําอะไรเลย เพิ่มโค้ดต่อไปนี้ลงในโค้ด

async function registerCredential() {
  // Fetch the credential creation options from the backend
  const credentialCreationOptionsFromServer = await _fetch(
    "/auth/credential-options",
    "POST"
  );
  // Decode the credential creation options
  const credentialCreationOptions = decodeServerOptions(
    credentialCreationOptionsFromServer
  );
  // Create a credential via the browser API; this will prompt the user to touch their security key or tap a button on their phone
  const credential = await navigator.credentials.create({
    publicKey: {
      ...credentialCreationOptions,
    }
  });
  // Encode the newly created credential to send it to the backend
  const encodedCredential = encodeCredential(credential);
  // Send the encoded credential to the backend for storage
  return await _fetch("/auth/credential", "POST", encodedCredential);
}

โปรดทราบว่าระบบส่งออกฟังก์ชันนี้ให้คุณแล้ว

สิ่งที่ registerCredential ทําได้มีดังนี้

  • ระบบจะดึงข้อมูลตัวเลือกการสร้างข้อมูลเข้าสู่ระบบจากเซิร์ฟเวอร์ (/auth/credential-options)
  • เนื่องจากตัวเลือกเซิร์ฟเวอร์กลับมาเข้ารหัสไว้ ระบบจึงใช้ฟังก์ชัน decodeServerOptions เพื่อถอดรหัสตัวเลือกยูทิลิตี
  • สร้างข้อมูลเข้าสู่ระบบด้วยการเรียก API เว็บ navigator.credential.create เมื่อมีการเรียกใช้ navigator.credential.create เบราว์เซอร์จะเข้าควบคุมและแจ้งให้ผู้ใช้เลือกคีย์ความปลอดภัย
  • ระบบจะถอดรหัสข้อมูลเข้าสู่ระบบที่สร้างขึ้นใหม่
  • โดยจะลงทะเบียนข้อมูลเข้าสู่ระบบฝั่งเซิร์ฟเวอร์ใหม่โดยส่งคําขอไปยัง /auth/credential ที่มีข้อมูลรับรองที่เข้ารหัส

ตัวอย่าง: ตรวจสอบรหัสเซิร์ฟเวอร์

registerCredential() ทําการเรียก 2 ครั้งไปยังเซิร์ฟเวอร์ ดังนั้นให้ใช้เวลาสักครู่เพื่อดูสิ่งที่กําลังเกิดขึ้นในแบ็กเอนด์

ตัวเลือกการสร้างข้อมูลเข้าสู่ระบบ

เมื่อไคลเอ็นต์ส่งคําขอไปยัง (/auth/credential-options) เซิร์ฟเวอร์จะสร้างออบเจ็กต์ตัวเลือกและส่งกลับไคลเอ็นต์

จากนั้นไคลเอ็นต์จะใช้การเรียกนี้ในการสร้างข้อมูลเข้าสู่ระบบจริง

navigator.credentials.create({
    publicKey: {
    // Options generated server-side
    ...credentialCreationOptions
// ...
}

ดังนั้น สิ่งที่<# class-ph-2-0">ใน credentialCreationOptions ใช้งานในช่วง registerCredential ของลูกค้าที่คุณติดตั้งใช้งานในขั้นตอนก่อนหน้ามีอะไรบ้าง

ดูรหัสเซิร์ฟเวอร์ในส่วน router.post("/credential-options", ...

มาลองดูพร็อพเพอร์ตี้แต่ละรายการกัน แต่นี่คือรายการที่น่าสนใจบางส่วนซึ่งคุณดูได้ในออบเจ็กต์ตัวเลือกในโค้ดเซิร์ฟเวอร์&#39, ที่สร้างขึ้นโดยใช้ไลบรารี fido2 และกลับมาที่ไคลเอ็นต์ในท้ายที่สุด

  • rpName และ rpId อธิบายองค์กรที่ลงทะเบียนและตรวจสอบสิทธิ์ผู้ใช้ โปรดทราบว่าใน WebAuthn ข้อมูลเข้าสู่ระบบจะกําหนดขอบเขตไว้เป็นบางโดเมน ซึ่งเป็นประโยชน์ด้านความปลอดภัย โดย rpName และ rpId จะใช้ขอบเขตข้อมูลเข้าสู่ระบบที่นี่ rpId ที่ถูกต้องเป็นชื่อโฮสต์ของเว็บไซต์ โปรดทราบว่าระบบจะอัปเดตฟีเจอร์เหล่านี้โดยอัตโนมัติเมื่อคุณแยกโปรเจ็กต์เริ่มต้น 🧘🏻 ♀️
  • excludeCredentials คือรายการข้อมูลเข้าสู่ระบบซึ่งสร้างข้อมูลเข้าสู่ระบบใหม่บน Authenticator ซึ่งมีข้อมูลเข้าสู่ระบบที่แสดงอยู่ใน excludeCredentials ไม่ได้ ใน Codelab ของเรา excludeCredentials คือรายการข้อมูลเข้าสู่ระบบที่มีอยู่สําหรับผู้ใช้รายนี้ ในการทําเช่นนี้และ user.id เราจะรับรองว่าข้อมูลเข้าสู่ระบบแต่ละรายการที่ผู้ใช้สร้างจะอยู่ใน Authenticator (คีย์ความปลอดภัย) ที่แตกต่างกัน วิธีนี้เป็นแนวทางปฏิบัติที่ดี เพราะหากผู้ใช้ลงทะเบียนข้อมูลรับรองหลายรายการ ผู้ใช้ก็จะใช้ Authenticator (คีย์ความปลอดภัย) ที่แตกต่างกัน ดังนั้น คีย์ความปลอดภัย 1 อันก็จะไม่ถูกล็อกผู้ใช้ออกจากบัญชี
  • authenticatorSelection ระบุประเภทของ Authenticator ที่คุณต้องการอนุญาตในเว็บแอปพลิเคชัน มาดูรายละเอียดเกี่ยวกับ authenticatorSelection กัน:
    • residentKey: preferred หมายความว่าแอปพลิเคชันนี้ไม่ได้บังคับใช้ข้อมูลรับรองที่ค้นพบฝั่งไคลเอ็นต์ได้ ข้อมูลรับรองที่ค้นพบได้ฝั่งไคลเอ็นต์คือข้อมูลเข้าสู่ระบบประเภทพิเศษที่ทําให้ตรวจสอบสิทธิ์ผู้ใช้ได้โดยไม่ต้องระบุรายแรก เราตั้งค่า preferred ที่นี่เพราะ Codelab นี้มุ่งเน้นที่การติดตั้งใช้งานขั้นพื้นฐาน โดยข้อมูลเข้าสู่ระบบที่ค้นหาได้มีไว้สําหรับขั้นตอนขั้นสูงกว่า
    • requireResidentKey จะใช้ได้กับ backwards-compatiability กับ WebAuthn v1 เท่านั้น
    • userVerification: preferred ซึ่งหมายความว่าหาก Authenticator รองรับการยืนยันผู้ใช้ เช่น หากเป็นคีย์ความปลอดภัยแบบไบโอเมตริกหรือคีย์ที่มีฟีเจอร์ PIN ในตัว ฝ่ายที่เชื่อถือจะขอการยืนยันเมื่อสร้างข้อมูลเข้าสู่ระบบ หาก Authenticator ไม่ได้ป้อนคีย์ความปลอดภัยพื้นฐาน เซิร์ฟเวอร์จะไม่ขอการยืนยันผู้ใช้
  • ​​pubKeyCredParam อธิบายพร็อพเพอร์ตี้การเข้ารหัสของข้อมูลเข้าสู่ระบบที่ต้องการตามลําดับ

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

ความท้าทาย

อีกสิ่งที่น่าสนใจอีกอย่างคือที่ req.session.challenge = options.challenge;

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

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

รหัสการจดทะเบียนข้อมูลเข้าสู่ระบบ

ดูรหัสเซิร์ฟเวอร์ในส่วน router.post("/credential", ...

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

แล้วยังไงต่อล่ะ

หนึ่งในสิ่งที่น่าจดจําที่สุดในโค้ดนี้คือการเรียกการยืนยันผ่าน fido2.verifyAttestationResponse

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

เพิ่มฟังก์ชันนี้ใน UI

ขณะนี้ฟังก์ชันในการสร้างข้อมูลเข้าสู่ระบบของคุณ ``registerCredential(), พร้อมแล้ว มาเริ่มให้บริการแก่ผู้ใช้กันเลย

คุณจะดําเนินการจากหน้าบัญชี เนื่องจากตําแหน่งนี้เป็นตําแหน่งปกติสําหรับการจัดการการตรวจสอบสิทธิ์

ในมาร์กอัป account.html&#39 ใต้ชื่อผู้ใช้ มี div ที่ว่างเปล่ามากและมีคลาสเลย์เอาต์ class="flex-h-between" เราจะใช้ div นี้สําหรับองค์ประกอบ UI ที่เกี่ยวข้องกับฟังก์ชันการทํางานของ 2FA

เพิ่ม div นี้:

  • ชื่อว่า "การตรวจสอบสิทธิ์แบบ 2 ปัจจัย"
  • ปุ่มสําหรับสร้างข้อมูลเข้าสู่ระบบ
 <div class="flex-h-between">
    <h3>
        Two-factor authentication
    </h3>
    <button class="create" id="registerButton" raised>
        ➕ Add a credential
    </button>
</div>

ใต้ div นี้ ให้เพิ่ม div ของข้อมูลเข้าสู่ระบบที่เราจะต้องใช้ในภายหลัง

<div class="flex-h-between">
(HTML you've just added)
</div>
<div id="credentials"></div>

ในฟังก์ชันสคริปต์ในบรรทัดของ account.html ให้นําเข้าฟังก์ชันที่เพิ่งสร้าง และเพิ่มฟังก์ชัน register ที่เรียกใช้สคริปต์นั้น รวมถึงเครื่องจัดการเหตุการณ์ที่แนบมากับปุ่มที่คุณเพิ่งสร้าง

// Set up the handler for the button that registers credentials
const registerButton = document.querySelector('#registerButton');
registerButton.addEventListener('click', register);

// Register a credential
async function register() {
  let user = {};
  try {
    const user = await registerCredential();
  } catch (e) {
    // Alert the user that something went wrong
    if (Array.isArray(e)) {
      alert(
        // `msg` not `message`, this is the key's name as per the express validator API
        `Registration failed. ${e.map((err) => `${err.msg} (${err.param})`)}`
      );
    } else {
      alert(`Registration failed. ${e}`);
    }
  }
}

แสดงข้อมูลเข้าสู่ระบบเพื่อให้ผู้ใช้เห็น

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

ซึ่งหน้าบัญชีก็เป็นหน้าที่เหมาะสม

ใน account.html ให้มองหาฟังก์ชันชื่อ updateCredentialList()

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

// Update the list that displays credentials
async function updateCredentialList() {
  // Fetch the latest credential list from the backend
  const response = await _fetch('/auth/credentials', 'GET');
  const credentials = response.credentials || [];
  // Generate the credential list as HTML and pass remove/rename functions as args
  const credentialListHtml = getCredentialListHtml(
    credentials,
    removeEl,
    renameEl
  );
  // Display the list of credentials in the DOM
  const list = document.querySelector('#credentials');
  render(credentialListHtml, list);
}    

ตอนนี้ก็ไม่เป็นไร removeEl และ renameEl คุณจะดูข้อมูลเกี่ยวกับโค้ดได้ในภายหลังใน Codelab นี้

เพิ่มการเรียกใช้ 1 รายการลงใน updateCredentialList ที่ตําแหน่งเริ่มต้นของสคริปต์ในหน้าภายใน account.html สําหรับการเรียกนี้ ระบบจะดึงข้อมูลเข้าสู่ระบบที่ใช้ได้เมื่อผู้ใช้ไปถึงหน้าบัญชีของตน

<script type="module">
    // ... (imports)
    // Initialize the credential list by updating it once on page load
    updateCredentialList();

ต่อไปนี้ให้เรียกใช้ updateCredentialList เมื่อ registerCredential เสร็จสิ้น เพื่อให้รายการแสดงข้อมูลเข้าสู่ระบบที่สร้างขึ้นใหม่

async function register() {
  let user = {};
  try {
    // ...
  } catch (e) {
    // ...
  }
  // Refresh the credential list to display the new credential
  await updateCredentialList();
}

ลองใช้เลย 👩🏻 💻

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

โปรดลองคำเหล่านี้

  • ออกจากระบบ
  • ลงชื่อเข้าสู่ระบบด้วยผู้ใช้และรหัสผ่าน ตามที่ได้กล่าวไว้ก่อนหน้านี้ รหัสผ่านไม่ได้ผ่านการตรวจสอบความถูกต้อง เพื่อทําให้สิ่งต่างๆ ง่ายขึ้นใน Codelab นี้ ป้อนรหัสผ่านที่ไม่ว่างเปล่า
  • เมื่อคุณอยู่ในหน้าบัญชี ให้คลิกเพิ่มข้อมูลเข้าสู่ระบบ
  • คุณควรแจ้งให้เสียบและแตะคีย์ความปลอดภัย ทําเลย
  • เมื่อสร้างข้อมูลเข้าสู่ระบบแล้ว ข้อมูลเข้าสู่ระบบจะแสดงในหน้าบัญชี
  • โหลดหน้าบัญชีซ้ํา ข้อมูลเข้าสู่ระบบควรจะแสดง
  • หากคุณมีคีย์ 2 คีย์ ให้ลองเพิ่มคีย์ความปลอดภัย 2 คีย์เป็นข้อมูลรับรอง ทั้งคู่ควรแสดง
  • ลองสร้างข้อมูลรับรอง 2 รายการที่มี Authenticator เดียวกัน (คีย์) คุณจะเห็นว่าไม่มีการรองรับเครื่องหมายเหล่านี้ เจตนาเกิดจากการที่เราใช้ excludeCredentials ในแบ็กเอนด์

7. เปิดใช้การตรวจสอบสิทธิ์แบบ 2 ปัจจัย

ผู้ใช้สามารถลงทะเบียนและยกเลิกการลงทะเบียนข้อมูลเข้าสู่ระบบได้ แต่ข้อมูลเข้าสู่ระบบจะแสดงและยังไม่มีการใช้งานจริง

ถึงเวลาแล้วที่จะนํามาใช้ และตั้งค่าการตรวจสอบสิทธิ์แบบ 2 ปัจจัยจริง

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

6ff49a7e520836d0.png

ขั้นตอนแบบ 2 ปัจจัยนี้

e7409946cd88efc7.png

ใช้การตรวจสอบสิทธิ์แบบ 2 ปัจจัย

เรามาเพิ่มฟังก์ชันการทํางานที่จําเป็นและสื่อสารกับแบ็กเอนด์กันก่อน โดยเราจะเพิ่มฟังก์ชันเหล่านี้ไว้ในฟรอนท์เอนด์ในขั้นตอนถัดไป

สิ่งที่คุณต้องทําที่นี่คือฟังก์ชันที่ตรวจสอบสิทธิ์ผู้ใช้ด้วยข้อมูลเข้าสู่ระบบ

ใน public/auth.client.js ให้มองหาฟังก์ชันเปล่า authenticateTwoFactor และเพิ่มโค้ดลงในโค้ดต่อไปนี้

async function authenticateTwoFactor() {
  // Fetch the 2F options from the backend
  const optionsFromServer = await _fetch("/auth/two-factor-options", "POST");
  // Decode them
  const decodedOptions = decodeServerOptions(optionsFromServer);
  // Get a credential via the browser API; this will prompt the user to touch their security key or tap a button on their phone
  const credential = await navigator.credentials.get({
    publicKey: decodedOptions
  });
  // Encode the credential
  const encodedCredential = encodeCredential(credential);
  // Send it to the backend for verification
  return await _fetch("/auth/authenticate-two-factor", "POST", {
    credential: encodedCredential
  });
}

โปรดทราบว่าเราได้ส่งออกฟังก์ชันนี้ให้คุณแล้ว ซึ่งเราต้องใช้ในขั้นตอนถัดไป

สิ่งที่ authenticateTwoFactor ทําได้มีดังนี้

  • โดยจะขอตัวเลือกการตรวจสอบสิทธิ์แบบ 2 ปัจจัยจากเซิร์ฟเวอร์ ระบบจะกําหนดตัวเลือกในเซิร์ฟเวอร์และขึ้นอยู่กับโมเดลความปลอดภัยของเว็บแอปพลิเคชัน เหมือนกับตัวเลือกการสร้างข้อมูลเข้าสู่ระบบที่เห็นก่อนหน้านี้ เจาะลึกรหัสเซิร์ฟเวอร์ใต้ router.post("/two-factors-options", ... เพื่อดูรายละเอียด
  • การเรียก navigator.credentials.get จะเป็นการอนุญาตให้เบราว์เซอร์เข้าควบคุมและแจ้งให้ผู้ใช้แทรกและแตะคีย์ที่ลงทะเบียนไว้ก่อนหน้านี้ ซึ่งอาจส่งผลให้ระบบเลือกข้อมูลรับรองสําหรับการดําเนินการตรวจสอบสิทธิ์แบบ 2 ปัจจัยนี้
  • จากนั้นระบบจะส่งข้อมูลเข้าสู่ระบบที่เลือกในคําขอแบ็กเอนด์เพื่อดึงข้อมูล("/auth/authenticate-two-ปัจจัย"` หากข้อมูลรับรองตรงกับผู้ใช้รายนั้น ก็จะตรวจสอบสิทธิ์ของข้อมูลเข้าสู่ระบบแล้ว

ตัวอย่าง: ตรวจสอบรหัสเซิร์ฟเวอร์

โปรดทราบว่า server.js จัดการการนําทางและสิทธิ์เข้าถึงบางอย่างไว้แล้ว เพื่อให้แน่ใจว่าหน้าบัญชีจะเข้าถึงได้โดยผู้ใช้ที่ตรวจสอบสิทธิ์แล้วเท่านั้น และดําเนินการเปลี่ยนเส้นทางที่จําเป็น

ตอนนี้ให้ดูที่รหัสเซิร์ฟเวอร์ภายใต้ router.post("/initialize-authentication", ...

มีจุดที่น่าสนใจ 2 ประการที่ควรทราบไว้ดังนี้

  • ทั้งรหัสผ่านและข้อมูลเข้าสู่ระบบจะได้รับการตรวจสอบพร้อมกันในขั้นตอนนี้ ซึ่งเป็นมาตรการรักษาความปลอดภัย: สําหรับผู้ใช้ที่ตั้งค่าการตรวจสอบสิทธิ์แบบ 2 ปัจจัย เราไม่ต้องการให้โฟลว์ของ UI มีลักษณะแตกต่างกันโดยขึ้นอยู่กับว่ารหัสผ่านถูกต้องหรือไม่ เราจึงตรวจสอบทั้งรหัสผ่านและข้อมูลเข้าสู่ระบบพร้อมกันในขั้นตอนนี้
  • ถ้าทั้งรหัสผ่านและข้อมูลเข้าสู่ระบบถูกต้อง เราจะตรวจสอบสิทธิ์ให้เสร็จสมบูรณ์โดยเรียก completeAuthentication(req, res); ว่าในทางปฏิบัติหมายความว่าเราจะเปลี่ยนเซสชันจากเซสชัน auth ชั่วคราวที่ผู้ใช้ยังไม่ได้ตรวจสอบสิทธิ์ ไปยังเซสชันหลัก main ที่ผู้ใช้ตรวจสอบสิทธิ์

รวมหน้าการตรวจสอบสิทธิ์แบบ 2 ปัจจัยไว้ในขั้นตอนของผู้ใช้

มองหาหน้าใหม่ second-factor.html ในโฟลเดอร์ views

ในนี้จะมีปุ่มบอกว่าใช้คีย์ความปลอดภัย แต่ตอนนี้จะไม่ดําเนินการใดๆ

ทําให้ปุ่มนี้เรียก authenticateTwoFactor() เมื่อคลิก

  • หาก authenticateTwoFactor() สําเร็จ ให้เปลี่ยนเส้นทางผู้ใช้ไปยังหน้าบัญชี
  • หากไม่สําเร็จ ให้แจ้งให้ผู้ใช้ทราบว่าเกิดข้อผิดพลาด ในแอปพลิเคชันจริง คุณจะนําข้อความแสดงข้อผิดพลาดที่มีประโยชน์มากขึ้นมาใช้เพื่อความเรียบง่ายในการสาธิตนี้ เราจะใช้เฉพาะการแจ้งเตือนหน้าต่างเท่านั้น
    <main>
...
    </main>
    <script type="module">
      import { authenticateTwoFactor, authStatuses } from "/auth.client.js";

      const button = document.querySelector("#authenticateButton");
      button.addEventListener("click", async e => {
        try {
          // Ask the user to authenticate with the second factor; this will trigger a browser prompt
          const response = await authenticateTwoFactor();
          const { authStatus } = response;
          if (authStatus === authStatuses.COMPLETE) {
            // The user is properly authenticated => Navigate to the Account page
            location.href = "/account";
          } else {
            throw new Error("Two-factor authentication failed");
          }
        } catch (e) {
          // Alert the user that something went wrong
          alert(`Two-factor authentication failed. ${e}`);
        }
      });
    </script>
  </body>
</html>

ใช้การตรวจสอบสิทธิ์แบบ 2 ปัจจัย

ตอนนี้คุณก็พร้อมที่จะเพิ่มขั้นตอนการตรวจสอบสิทธิ์แบบ 2 ปัจจัยแล้ว

สิ่งที่ต้องทําตอนนี้คือเพิ่มขั้นตอนนี้จาก index.html สําหรับผู้ใช้ที่ได้กําหนดค่าการตรวจสอบสิทธิ์แบบ 2 ปัจจัย

ไฟล์ 322a5c49d865a0d8.png

ใน index.html ด้านล่าง location.href = "/account"; ให้เพิ่มโค้ดที่จะนําผู้ใช้ไปยังหน้าการตรวจสอบสิทธิ์ปัจจัยที่ 2 แบบมีเงื่อนไข หากผู้ใช้ตั้งค่าการยืนยันแบบ 2 ขั้นตอนไว้

ใน Codelab นี้ การสร้างข้อมูลเข้าสู่ระบบจะเลือกให้ผู้ใช้ใช้การตรวจสอบสิทธิ์แบบ 2 ปัจจัยโดยอัตโนมัติ

โปรดทราบว่า server.js ใช้การตรวจสอบเซสชันฝั่งเซิร์ฟเวอร์ด้วยเพื่อให้มั่นใจว่าจะมีเพียงผู้ใช้ที่ตรวจสอบสิทธิ์แล้วเท่านั้นที่เข้าถึง account.html ได้

const { authStatus } = response;
if (authStatus === authStatuses.COMPLETE) {
  // The user is properly authenticated => navigate to account
  location.href = '/account';
} else if (authStatus === authStatuses.NEED_SECOND_FACTOR) {
  // Navigate to the two-factor-auth page because two-factor-auth is set up for this user
  location.href = '/second-factor';
}

ลองใช้เลย 👩🏻 💻

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

8. ทําให้ข้อมูลเข้าสู่ระบบใช้งานง่ายขึ้น

คุณใช้งานฟังก์ชันพื้นฐานของการตรวจสอบสิทธิ์แบบ 2 ปัจจัยด้วยคีย์ความปลอดภัยได้แล้ว 🚀

แต่... คุณสังเกตเห็นไหม

ในขณะนี้ ข้อมูลเข้าสู่ระบบของเราไม่อํานวยความสะดวกมากนัก เนื่องจากรหัสข้อมูลเข้าสู่ระบบและคีย์สาธารณะเป็นสตริงยาวๆ ซึ่งไม่มีประโยชน์ในการจัดการข้อมูลเข้าสู่ระบบ มนุษย์เราไม่ดีกับสตริงและตัวเลขยาวๆ 🤖

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

ลองดูที่ usernameCredential

ระบบได้เพิ่มฟังก์ชันในการเปลี่ยนชื่อข้อมูลเข้าสู่ระบบให้คุณในโค้ดเริ่มต้นใน auth.client.js เพื่อช่วยประหยัดเวลาให้คุณใช้ฟังก์ชันนี้ โดยที่ไม่ต้องทําอะไรมากเกินไป

async function renameCredential(credId, newName) {
  const params = new URLSearchParams({
    credId,
    name: newName
  });
  return _fetch(
    `/auth/credential?${params}`,
    "PUT"
  );
}

นี่เป็นการเรียกการอัปเดตฐานข้อมูลเป็นประจํา: ไคลเอ็นต์จะส่งคําขอ PUT ไปยังแบ็กเอนด์ โดยมีรหัสข้อมูลเข้าสู่ระบบและชื่อใหม่ของข้อมูลเข้าสู่ระบบนั้น

ใช้ชื่อข้อมูลเข้าสู่ระบบที่กําหนดเอง

ใน account.html ให้สังเกตฟังก์ชันว่าง rename

เพิ่มไปยังโค้ดต่อไปนี้

// Rename a credential
async function rename(credentialId) {
  // Let the user input a new name
  const newName = window.prompt(`Name this credential:`);
  // Rename only if the user didn't cancel AND didn't enter an empty name
  if (newName && newName.trim()) {
    try {
      // Make the backend call to rename the credential (the name is sanitized) server-side
      await renameCredential(credentialId, newName);
    } catch (e) {
      // Alert the user that something went wrong
      if (Array.isArray(e)) {
        alert(
          // `msg` not `message`, this is the key's name as per the express validator API
          `Renaming failed. ${e.map((err) => `${err.msg} (${err.param})`)}`
        );
      } else {
        alert(`Renaming failed. ${e}`);
      }
    }
    // Refresh the credential list to display the new name
    await updateCredentialList();
  }
}

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

ใช้ฟังก์ชัน rename ใน register() เพื่อให้ผู้ใช้ตั้งชื่อข้อมูลเข้าสู่ระบบเมื่อลงทะเบียนได้:

async function register() {
  let user = {};
  try {
    const user = await registerCredential();
    // Get the latest credential's ID (newly created credential)
    const allUserCredentials = user.credentials;
    const newCredential = allUserCredentials[allUserCredentials.length - 1];
    // Rename it
    await rename(newCredential.credId);
  } catch (e) {
    // ...
  }
  // Refresh the credential list to display the new credential
  await updateCredentialList();
}

โปรดทราบว่าระบบจะตรวจสอบอินพุตของผู้ใช้และตรวจสอบความถูกต้องของแบ็กเอนด์

  check("name")
    .trim()
    .escape()

แสดงชื่อข้อมูลเข้าสู่ระบบ

ไปที่ getCredentialHtml ใน templates.js

โปรดทราบว่ามีโค้ดที่แสดงชื่อข้อมูลเข้าสู่ระบบที่ด้านบนของการ์ดข้อมูลเข้าสู่ระบบอยู่แล้ว

// Register credential
const getCredentialHtml = (credential, removeEl, renameEl) => {
 const { name, credId, publicKey } = credential;
 return html`
    <div class="credential-card">
      <div class="credential-name">
        ${name
          ? html`
              ${name}
            `
          : html`
              <span class="unnamed">(Unnamed)</span>
            `}
      </div>
     // ...
    </div>
  `;
};

ลองใช้เลย 👩🏻 💻

  • สร้างข้อมูลรับรอง
  • ระบบจะแจ้งให้คุณตั้งชื่อ
  • ป้อนชื่อใหม่แล้วคลิกตกลง
  • ข้อมูลรับรองเปลี่ยนชื่อแล้ว
  • แล้วตรวจสอบว่าสิ่งต่างๆ ทํางานได้อย่างราบรื่นด้วย เมื่อปล่อยช่องชื่อว่างไว้

เปิดใช้การเปลี่ยนชื่อข้อมูลรับรอง

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

ใน account.html ให้มองหาฟังก์ชันว่างที่อยู่ไกลสุด renameEl แล้วเพิ่มลงในโค้ดต่อไปนี้

// Rename a credential via HTML element
async function renameEl(el) {
  // Define the ID of the credential to update
  const credentialId = el.srcElement.dataset.credentialId;
  // Rename the credential
  await rename(credentialId);
  // Refresh the credential list to display the new name
  await updateCredentialList();
}

ตอนนี้ ใน templates.js ของ templates.js ส่วน div ของ class="flex-end" ให้เพิ่มโค้ดต่อไปนี้ โค้ดนี้จะเพิ่มปุ่มเปลี่ยนชื่อลงในเทมเพลตข้อมูลเข้าสู่ระบบ เมื่อคลิก ปุ่มนั้นจะเรียกฟังก์ชัน renameEl ที่เราสร้างให้

const getCredentialHtml = (credential, removeEl, renameEl) => {
// ...
 <div class="flex-end">
  <button
    data-credential-id="${credId}"
    @click="${renameEl}"
    class="secondary right"
  >
   Rename
  </button>
 </div>
 // ...
  `;
};

ลองใช้เลย 👩🏻 💻

  • คลิกเปลี่ยนชื่อ
  • ป้อนชื่อใหม่เมื่อระบบแจ้ง
  • คลิกตกลง
  • เปลี่ยนชื่อข้อมูลรับรองเรียบร้อยแล้ว รายการควรจะอัปเดตโดยอัตโนมัติ
  • การโหลดหน้าเว็บซ้ําควรแสดงชื่อใหม่ (ซึ่งแสดงให้เห็นว่าชื่อใหม่ยังคงอยู่ในฝั่งเซิร์ฟเวอร์)

แสดงวันที่สร้างข้อมูลเข้าสู่ระบบ

วันที่สร้างจะไม่แสดงในข้อมูลเข้าสู่ระบบที่สร้างผ่าน navigator.credential.create()

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

ใน templates.js ภายใน class="creation-date" div ให้เพิ่มข้อมูลต่อไปนี้เพื่อแสดงข้อมูลวันที่สร้างแก่ผู้ใช้

<div class="creation-date">
  <label>Created:</label>
  <div class="info">
    ${new Date(creationDate).toLocaleDateString()}
    ${new Date(creationDate).toLocaleTimeString()}
  </div>
</div>

9. ทําให้โค้ดของคุณเหมาะกับการใช้งานในอนาคต

จนถึงตอนนี้ เราขอให้ผู้ใช้ลงทะเบียน Authenticator สําหรับการโรมมิ่งแบบง่ายที่ใช้เป็นปัจจัยที่ 2 ระหว่างการลงชื่อเข้าใช้เท่านั้น

วิธีการขั้นสูงขึ้นอีกขั้นตอนหนึ่งคือ การใช้ Authenticator ประเภทที่ทรงพลังมากขึ้น นั่นคือผู้ใช้ Authenticator แบบโรมมิ่งเพื่อยืนยันผู้ใช้ (UVRA) UVRA สามารถให้ปัจจัยการตรวจสอบสิทธิ์สองแบบและการป้องกันฟิชชิงในขั้นตอนการลงชื่อเข้าใช้ขั้นตอนเดียว

โดยหลักการแล้ว คุณควรรองรับทั้ง 2 แนวทาง ในการทําเช่นนี้ คุณต้องปรับแต่งประสบการณ์ของผู้ใช้โดยทําดังนี้

  • หากผู้ใช้มีเฉพาะเครื่องมือตรวจสอบสิทธิ์การโรมมิ่งแบบง่าย (ไม่ยืนยันผู้ใช้) โปรดให้ผู้ใช้ใช้เครื่องมือนี้เพื่อรับการเปิดเครื่องบัญชีที่ป้องกันฟิชชิงได้ แต่จะต้องพิมพ์ชื่อผู้ใช้และรหัสผ่านด้วย ซึ่งเป็นสิ่งที่ Codelab ของเราทําอยู่แล้ว
  • หากผู้ใช้รายอื่นมี Authenticator สําหรับการตรวจสอบสิทธิ์แบบผ่านการยืนยันผู้ใช้ขั้นสูงกว่า ผู้ใช้เหล่านั้นจะสามารถข้ามขั้นตอนรหัสผ่านหรือแม้แต่ขั้นตอนชื่อผู้ใช้ระหว่างขั้นตอนการเปิดเครื่องบัญชีได้

ดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในการบูตบัญชีป้องกันฟิชชิงด้วยตัวเลือกการลงชื่อเข้าใช้แบบไร้รหัสผ่าน

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

คุณต้องมี 2 สิ่งต่อไปนี้

  • ตั้งค่า residentKey: preferred ในการตั้งค่าแบ็กเอนด์ ขั้นตอนนี้ทําให้คุณอยู่แล้ว
  • ตั้งค่าวิธีดูว่ามีการสร้างข้อมูลเข้าสู่ระบบที่ค้นพบได้ (หรือที่เรียกว่าคีย์ผู้อยู่อาศัย) หรือไม่

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

  • ค้นหาค่าของ credProps เมื่อสร้างข้อมูลเข้าสู่ระบบ (credProps: true)
  • ค้นหาค่าของ transports เมื่อสร้างข้อมูลเข้าสู่ระบบ วิธีนี้จะช่วยให้คุณทราบว่าแพลตฟอร์มที่สําคัญรองรับฟังก์ชันการทํางานของ UVRA หรือไม่ เช่น เป็นโทรศัพท์มือถือ เป็นต้น
  • เก็บค่าของ credProps และ transports ในแบ็กเอนด์ ซึ่งคุณทําไว้แล้วในโค้ดเริ่มต้น ลองดูที่ auth.js ถ้าคุณอยากรู้

มารับค่าของ credProps และ transports แล้วส่งไปยังแบ็กเอนด์กัน ใน auth.client.js ให้แก้ไข registerCredential ดังนี้

  • เพิ่มช่อง extensions เมื่อโทรหา navigator.credentials.create
  • ตั้งค่า encodedCredential.transports และ encodedCredential.credProps ก่อนที่จะส่งข้อมูลเข้าสู่ระบบไปยังแบ็กเอนด์เพื่อจัดเก็บข้อมูล

registerCredential ควรมีลักษณะต่อไปนี้

async function registerCredential() {
  // Fetch the credential creation options from the backend
  const credentialCreationOptionsFromServer = await _fetch(
    '/auth/credential-options',
    'POST'
  );
  // Decode the credential creation options
  const credentialCreationOptions = decodeServerOptions(
    credentialCreationOptionsFromServer
  );
  // Create a credential via the browser API; this will prompt the user
  const credential = await navigator.credentials.create({
    publicKey: {
      ...credentialCreationOptions,
      extensions: {
        credProps: true,
      },
    },
  });
  // Encode the newly created credential to send it to the backend
  const encodedCredential = encodeCredential(credential);
  // Set transports and credProps for more advanced user flows
  encodedCredential.transports = credential.response.getTransports();
  encodedCredential.credProps =
    credential.getClientExtensionResults().credProps;
  // Send the encoded credential to the backend for storage
  return await _fetch('/auth/credential', 'POST', encodedCredential);
}

10. ดูแลให้การรองรับข้ามเบราว์เซอร์

รองรับเบราว์เซอร์ที่ไม่ใช่ Chromium

ในฟังก์ชัน registerCredential ของ public/auth.client.js&#39 เราจะเรียกใช้ credential.response.getTransports() ในข้อมูลเข้าสู่ระบบที่สร้างขึ้นใหม่เพื่อบันทึกข้อมูลนี้ในแบ็กเอนด์เป็นคําแนะนําเซิร์ฟเวอร์

อย่างไรก็ตาม ขณะนี้ getTransports() ไม่ได้รับการนําไปใช้ในทุกเบราว์เซอร์ (ต่างจาก getClientExtensionResults ที่เบราว์เซอร์ต่างๆ รองรับ): การเรียก getTransports() จะแสดงข้อผิดพลาดใน Firefox และ Safari ซึ่งจะป้องกันไม่ให้มีการสร้างข้อมูลเข้าสู่ระบบในเบราว์เซอร์เหล่านี้

เพื่อให้โค้ดทํางานในเบราว์เซอร์หลักทั้งหมด ให้รวมการเรียก encodedCredential.transports ในเงื่อนไขดังนี้

if (credential.response.getTransports) {
  encodedCredential.transports = credential.response.getTransports();
}

โปรดทราบว่า transports ตั้งค่าเซิร์ฟเวอร์เป็น transports || [] ใน Firefox และ Safari รายการ transports จะไม่ใช่ undefined แต่เป็นรายการว่างเปล่า [] ซึ่งจะป้องกันข้อผิดพลาด

เตือนผู้ใช้ที่ใช้เบราว์เซอร์ที่ไม่รองรับ WebAuthn

1e9c1be837d66ce8.png

แม้ว่า WebAuthn จะรองรับเบราว์เซอร์หลักๆ ทั้งหมด แต่เราขอแนะนําให้แสดงคําเตือนในเบราว์เซอร์ที่ไม่รองรับ WebAuthn

ใน index.html ให้สังเกต div นี้:

<div id="warningbanner" class="invisible">
⚠️ Your browser doesn't support WebAuthn. Open this demo in Chrome, Edge, Firefox or Safari.
</div>

ในสคริปต์ในหน้าของ index.html&#39 ให้เพิ่มโค้ดต่อไปนี้เพื่อแสดงแบนเนอร์ในเบราว์เซอร์ที่ไม่รองรับ WebAuthn:

// Display a banner in browsers that don't support WebAuthn
if (!window.PublicKeyCredential) {
  document.querySelector('#warningbanner').classList.remove('invisible');
}

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

11. เยี่ยมมาก!

✨คุณ'เสร็จแล้ว!

คุณได้ใช้การตรวจสอบสิทธิ์แบบ 2 ปัจจัยกับคีย์ความปลอดภัยแล้ว

ใน Codelab นี้ เราได้พูดถึงข้อมูลเบื้องต้นแล้ว หากต้องการสํารวจ WebAuthn สําหรับ 2FA เพิ่มเติม ลองดูไอเดียต่อไปนี้

  • เพิ่ม "ใช้ล่าสุด" ในการ์ดข้อมูลรับรอง ซึ่งเป็นข้อมูลที่มีประโยชน์สําหรับผู้ใช้ในการพิจารณาว่ามีการใช้คีย์ความปลอดภัยที่ระบุอยู่หรือไม่ โดยเฉพาะอย่างยิ่งหากผู้ใช้ลงทะเบียนคีย์หลายคีย์
  • ใช้การจัดการข้อผิดพลาดที่มีประสิทธิภาพมากขึ้นและข้อความแสดงข้อผิดพลาดที่แม่นยํามากขึ้น
  • ดูหัวข้อ auth.js แล้วสํารวจสิ่งที่จะเกิดขึ้นเมื่อคุณเปลี่ยนแปลงauthSettingsบางส่วน โดยเฉพาะอย่างยิ่งเมื่อใช้คีย์ที่รองรับการยืนยันผู้ใช้