แนวทางปฏิบัติแนะนำสำหรับแบบฟอร์ม SMS OTP

ดูวิธีเพิ่มประสิทธิภาพแบบฟอร์ม SMS OTP และปรับปรุงประสบการณ์ของผู้ใช้

การขอให้ผู้ใช้ระบุ OTP (รหัสผ่านที่สามารถใช้งานได้เพียงครั้งเดียว) ที่ส่งผ่าน SMS เป็นวิธีการทั่วไปในการยืนยันหมายเลขโทรศัพท์ของผู้ใช้ ตัวอย่างการใช้ SMS OTP มีดังนี้

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

โพสต์นี้จะอธิบายแนวทางปฏิบัติแนะนำในการสร้างแบบฟอร์ม SMS OTP สำหรับกรณีการใช้งานข้างต้น

เช็กลิสต์

ทำตามขั้นตอนต่อไปนี้เพื่อมอบประสบการณ์การใช้งานที่ดีที่สุดแก่ผู้ใช้ด้วย SMS OTP

  • ใช้องค์ประกอบ <input> กับ
    • type="text"
    • inputmode="numeric"
    • autocomplete="one-time-code"
  • ใช้ @BOUND_DOMAIN #OTP_CODE เป็นบรรทัดสุดท้ายของข้อความ SMS จาก OTP
  • ใช้ WebOTP API

ใช้องค์ประกอบ <input>

การใช้แบบฟอร์มที่มีองค์ประกอบ <input> เป็นแนวทางปฏิบัติแนะนำที่สำคัญที่สุดซึ่งคุณทำได้เนื่องจากใช้ได้กับทุกเบราว์เซอร์ แม้ว่าคำแนะนำอื่นๆ จากโพสต์นี้จะใช้กับบางเบราว์เซอร์ไม่ได้ แต่ผู้ใช้จะยังป้อนและส่ง OTP ด้วยตัวเองได้

<form action="/verify-otp" method="POST">
  <input type="text"
         inputmode="numeric"
         autocomplete="one-time-code"
         pattern="\d{6}"
         required>
</form>

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

type="text"

เนื่องจาก OTP มักจะเป็นตัวเลข 5 หรือ 6 หลัก การใช้ type="number" สำหรับช่องป้อนข้อมูลจึงอาจดูเป็นเรื่องง่ายเพราะเป็นการเปลี่ยนแป้นพิมพ์บนอุปกรณ์เคลื่อนที่ให้เป็นเฉพาะตัวเลข เราไม่แนะนำให้ทำเช่นนี้เนื่องจากเบราว์เซอร์คาดหวังว่าช่องอินพุตเป็นตัวเลขที่นับได้ แทนที่จะเป็นตัวเลขจำนวนมากที่เรียงตามลำดับ ซึ่งอาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิด การใช้ type="number" จะทำให้ปุ่มขึ้นและลงแสดงข้างช่องป้อนข้อมูล การกดปุ่มเหล่านี้จะเพิ่มหรือลดจำนวนตัวเลข และอาจนำเลข 0 ที่อยู่ก่อนหน้าออก

โปรดใช้ type="text" แทน วิธีนี้จะไม่เปลี่ยนแป้นพิมพ์บนอุปกรณ์เคลื่อนที่เป็นตัวเลขเท่านั้น แต่ไม่เป็นไรเพราะเคล็ดลับถัดไปในการใช้ inputmode="numeric" จะทำหน้าที่นี้

inputmode="numeric"

ใช้ inputmode="numeric" เพื่อเปลี่ยนแป้นพิมพ์บนอุปกรณ์เคลื่อนที่เป็นตัวเลขเท่านั้น

บางเว็บไซต์ใช้ type="tel" สำหรับช่องป้อนข้อมูล OTP เนื่องจากยังเปลี่ยนแป้นพิมพ์ในอุปกรณ์เคลื่อนที่เป็นตัวเลขเท่านั้น (รวมถึง * และ #) เมื่อโฟกัสอยู่ การแฮ็กนี้เคยใช้ในอดีตที่ไม่มีการรองรับ inputmode="numeric" อย่างกว้างขวาง เนื่องจาก Firefox เริ่มรองรับ inputmode="numeric" จึงไม่จำเป็นต้องใช้การแฮ็ก type="tel" ที่ไม่ถูกต้องตามความหมาย

autocomplete="one-time-code"

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

เมื่อใช้ autocomplete="one-time-code" ทุกครั้งที่ผู้ใช้ได้รับข้อความ SMS ขณะกรอกแบบฟอร์ม ระบบปฏิบัติการจะแยกวิเคราะห์ OTP ใน SMS โดยใช้วิธีเก่า และแป้นพิมพ์จะแนะนำ OTP ให้ผู้ใช้ป้อน โดยจะใช้ได้เฉพาะกับ Safari 12 ขึ้นไปใน iOS, iPadOS และ macOS แต่เราขอแนะนำเป็นอย่างยิ่งให้ใช้เนื่องจากเป็นวิธีที่ง่ายในการปรับปรุงประสบการณ์การใช้งาน SMS OTP ในแพลตฟอร์มเหล่านั้น

`autocomplete="one-time-code"` ทํางานอยู่

autocomplete="one-time-code" ช่วยปรับปรุงประสบการณ์ของผู้ใช้ แต่คุณยังทำอย่างอื่นได้ด้วยการตรวจสอบว่าข้อความ SMS เป็นไปตามรูปแบบข้อความที่เชื่อมโยงกับต้นทาง

จัดรูปแบบข้อความ SMS

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

กฎการจัดรูปแบบนั้นง่ายมาก คือให้ส่งข้อความ SMS ให้เสร็จโดยใส่โดเมนของผู้รับซึ่งขึ้นต้นด้วย @ และ OTP นำหน้าด้วย #

เช่น

Your OTP is 123456

@web-otp.glitch.me #123456

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

การใช้รูปแบบนี้มีประโยชน์ 2 ประการ ดังนี้

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

เมื่อเว็บไซต์ใช้ autocomplete="one-time-code" Safari ที่ใช้ iOS 14 ขึ้นไปจะแนะนำ OTP ตามกฎข้างต้น

รูปแบบข้อความ SMS นี้ยังมีประโยชน์ต่อเบราว์เซอร์อื่นๆ ที่ไม่ใช่ Safari ด้วย Chrome, Opera และ Vivaldi ใน Android ยังรองรับกฎโค้ดแบบใช้ครั้งเดียวแบบผูกกับต้นทางกับ WebOTP API ด้วย แต่จะไม่ใช่ผ่าน autocomplete="one-time-code"

ใช้ WebOTP API

WebOTP API ให้สิทธิ์เข้าถึง OTP ที่ได้รับในข้อความ SMS เมื่อเรียกใช้ navigator.credentials.get() ด้วยประเภท otp (OTPCredential) เมื่อ transport มี sms เว็บไซต์จะรอ SMS ที่สอดคล้องกับรหัสแบบใช้ครั้งเดียวที่มีผูกไว้กับต้นทางเพื่อรับการนำส่งและให้สิทธิ์เข้าถึงโดยผู้ใช้ เมื่อระบบส่ง OTP ไปยัง JavaScript เว็บไซต์จะสามารถใช้ OTP ในรูปแบบหรือ POST ไปยังเซิร์ฟเวอร์โดยตรง

navigator.credentials.get({
  otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);
การทำงานของ WebOTP API

ดูวิธีใช้ WebOTP API โดยละเอียดในยืนยันหมายเลขโทรศัพท์บนเว็บด้วย WebOTP API หรือคัดลอกและวางข้อมูลโค้ดต่อไปนี้ (ตรวจสอบว่าองค์ประกอบ <form> มีการตั้งค่าแอตทริบิวต์ action และ method อย่างถูกต้อง)

// Feature detection
if ('OTPCredential' in window) {
  window.addEventListener('DOMContentLoaded', e => {
    const input = document.querySelector('input[autocomplete="one-time-code"]');
    if (!input) return;
    // Cancel the WebOTP API if the form is submitted manually.
    const ac = new AbortController();
    const form = input.closest('form');
    if (form) {
      form.addEventListener('submit', e => {
        // Cancel the WebOTP API.
        ac.abort();
      });
    }
    // Invoke the WebOTP API
    navigator.credentials.get({
      otp: { transport:['sms'] },
      signal: ac.signal
    }).then(otp => {
      input.value = otp.code;
      // Automatically submit the form when an OTP is obtained.
      if (form) form.submit();
    }).catch(err => {
      console.log(err);
    });
  });
}

รูปภาพโดย Jason Leung ใน Unsplash