簡訊動態密碼表單最佳做法

一般來說,系統會傳送簡訊,要求使用者提供一次性密碼 (OTP) 來確認身分。簡訊 OTP 的用途包括:

  • 雙重驗證。除了使用者名稱和密碼,簡訊 OTP 也能做為強烈信號,證明帳戶屬於收到簡訊 OTP 的使用者。
  • 驗證電話號碼。部分服務會將電話號碼做為使用者的主要 ID。在這些服務中,使用者可以輸入電話號碼和透過簡訊收到的 OTP,證明自己的身分。有時會與 PIN 碼搭配使用,構成雙重驗證。
  • 帳戶救援。使用者無法登入帳戶時,必須有方法可以復原帳戶。常見的帳戶救援方法包括傳送電子郵件到註冊的電子郵件地址,或是傳送簡訊動態密碼到電話號碼。
  • 確認付款:在付款系統中,部分銀行或信用卡發卡機構會基於安全考量,要求付款人進行額外驗證。簡訊 OTP 通常用於此目的。

請繼續閱讀,瞭解如何針對這些用途建構簡訊 OTP 表單的最佳做法。

檢查清單

如要透過簡訊驗證碼提供最佳使用者體驗,請按照下列步驟操作:

  • 使用 <input> 元素,並搭配:
    • type="text"
    • inputmode="numeric"
    • autocomplete="one-time-code"
  • 在 OTP 簡訊的最後一行使用 @BOUND_DOMAIN #OTP_CODE
  • 使用 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 通常是五或六位數的號碼,因此使用 type="number" 做為輸入欄位可能很直覺,因為這樣會將行動裝置鍵盤變更為僅顯示數字。我們不建議這麼做,因為瀏覽器會將輸入欄位視為可計數的數字,而非一連串的數字,這可能會導致非預期行為。使用 type="number" 會在輸入欄位旁顯示向上和向下按鈕;按下這些按鈕會增加或減少數字,並可能移除前置零。

請改用 type="text"。這不會將行動裝置鍵盤變成只顯示數字,但沒關係,因為下一個使用 inputmode="numeric" 的提示會執行這項工作。

inputmode="numeric"

使用 inputmode="numeric" 將行動裝置鍵盤切換為只顯示數字。

部分網站會將 type="tel" 用於動態密碼輸入欄位,因為這樣做也能在焦點移至該欄位時,將行動裝置鍵盤切換為僅顯示數字 (包括 *#)。過去在 inputmode="numeric" 尚未廣泛支援時,會使用這項變通方法。由於 Firefox 開始支援 inputmode="numeric",因此無須使用語意不正確的 type="tel" 駭客。

autocomplete="one-time-code"

autocomplete 屬性可讓開發人員指定瀏覽器提供自動完成輔助功能的權限,並告知瀏覽器欄位中預期的資訊類型。

有了 autocomplete="one-time-code",每當使用者在開啟表單時收到簡訊,作業系統就會以啟發式方式剖析簡訊中的一次性密碼,鍵盤也會建議使用者輸入該密碼。這項功能僅適用於 iOS、iPadOS 和 macOS 上的 Safari 12 以上版本,但我們強烈建議使用,因為這是改善這些平台簡訊 OTP 體驗的絕佳方式。

autocomplete="one-time-code" 實際運作情況。

autocomplete="one-time-code"可提升使用者體驗,但您還可以確保簡訊符合以來源為準的訊息格式,進一步改善體驗。

選用屬性

選用屬性包括:

  • pattern 指定輸入的 OTP 必須符合的格式。使用規則運算式指定比對模式。舉例來說,\d{6} 會將一次性密碼限制為六位數的字串。如要進一步瞭解 pattern,請參閱「使用 JavaScript 進行更複雜的即時驗證」。
  • required 表示使用者必須填寫該欄位。

如需更多建議,請參閱登入表單最佳做法

設定簡訊文字格式

為提升輸入動態密碼的使用者體驗,我們將按照簡訊傳送的來源繫結一次性驗證碼規格,調整動態密碼的輸入方式。

格式規則的核心如下:在簡訊結尾加上收件者網域 (前面加上 @),以及動態密碼 (前面加上 #)。

例如:

Your OTP is 123456

@web-otp.glitch.me #123456

採用標準格式的 OTP 訊息可讓系統更輕鬆可靠地擷取驗證碼。 將一次性密碼與網站建立關聯,可避免使用者受騙,將密碼提供給惡意網站。

精確格式規則

精確規則如下:

  • 訊息開頭可加入易讀文字,其中包含至少一個數字的四到十個英數字元字串,最後一行則保留給網址和一次性密碼。
  • 呼叫 API 的網站網址網域部分必須以 @ 開頭。
  • 網址必須包含 #,後面接著 OTP。字元數不得超過 140 個。

使用這種格式有幾個優點:

  • OTP 會與網域綁定。如果使用者位於簡訊中指定的網域以外的網域,系統就不會顯示 OTP 建議。 這也有助於降低網路釣魚攻擊和帳戶遭盜用的風險。
  • 現在瀏覽器可以穩定擷取 OTP,不必依賴神秘且不穩定的啟發式演算法。

如果網站使用 autocomplete="one-time-code",iOS 14 以上版本的 Safari 會根據下列規則建議 OTP。

這種簡訊格式也能讓 Safari 以外的瀏覽器受益。Android 上的 Chrome、Opera 和 Vivaldi 也支援 WebOTP API 的來源繫結一次性代碼規則,但不是透過 autocomplete="one-time-code"

使用 WebOTP API

WebOTP API 可存取簡訊中收到的 OTP。呼叫 navigator.credentials.get() 並使用 otp 型別 (OTPCredential),其中 transport 包含 sms,網站就會等待符合來源繫結一次性代碼的簡訊送達,並由使用者授予存取權。將一次性密碼傳遞至 JavaScript 後,網站即可在表單中使用該密碼,或直接將密碼 POST 至伺服器。

navigator.credentials.get({
  otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);
WebOTP API in action.

如要詳細瞭解如何使用 WebOTP API,請參閱「使用 WebOTP API 驗證網頁上的電話號碼」,或複製並貼上下列程式碼片段。請務必在 <form> 中設定 actionmethod 屬性。

// 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);
    });
  });
}