使用 WebOTP API 驗證網路上的電話號碼

協助使用者處理透過簡訊接收的一次性密碼

什麼是 WebOTP API?

現今,全球大多數人都有行動裝置,開發人員也普遍使用電話號碼做為服務使用者的 ID。

驗證電話號碼的方式有很多種,但最常見的是透過簡訊傳送隨機產生的動態密碼。將這個驗證碼傳送回開發人員的伺服器,即可證明您擁有該電話號碼的控制權。

這項概念已在許多情境中部署,以達成下列目標:

  • 電話號碼,做為使用者的 ID。註冊新服務時,部分網站會要求提供電話號碼,而非電子郵件地址,並將電話號碼做為帳戶 ID。
  • 兩步驟驗證。登入時,網站除了會要求輸入密碼或其他知識因素,還會透過簡訊傳送一次性驗證碼,以提升安全性。
  • 付款確認。使用者付款時,要求提供透過簡訊傳送的一次性代碼,有助於驗證使用者意圖。

目前程序會造成使用者不便。在簡訊中尋找一次性密碼,然後複製並貼到表單中,這項作業相當麻煩,會降低重要使用者歷程的轉換率。許多全球頂尖開發人員長期以來都希望簡化這項程序。Android 提供可執行這項作業的 APIiOSSafari 也是如此。

WebOTP API 可讓應用程式接收與應用程式網域綁定的特定格式訊息。您可從 SMS 訊息以程式輔助方式取得動態密碼,並更輕鬆地驗證使用者的電話號碼。

實例觀摩

假設使用者想透過網站驗證電話號碼,網站會透過簡訊傳送動態密碼給使用者,使用者輸入訊息中的動態密碼,即可驗證電話號碼擁有權。

如影片所示,使用者只要輕觸一下,就能透過 WebOTP API 完成這些步驟。收到簡訊後,系統會彈出底部功能表,提示使用者驗證電話號碼。點選底部功能表上的「驗證」按鈕後,瀏覽器會將一次性密碼貼到表單中,並提交表單,使用者無須按下「繼續」

下圖說明整個程序。

WebOTP API 圖表

親自試用示範。系統不會要求你提供電話號碼,也不會傳送簡訊到你的裝置,但你可以從其他裝置傳送簡訊,方法是複製示範畫面中顯示的文字。這是因為使用 WebOTP API 時,傳送者是誰並不重要。

  1. 在 Android 裝置上使用 Chrome 84 以上版本,前往 https://chrome.dev/web-otp-demo
  2. 使用另一部手機傳送以下簡訊給你的手機。
Your OTP is: 123456.

@chrome.dev #123456

你是否已收到簡訊,並看到提示在輸入區域輸入驗證碼? 這就是 WebOTP API 的運作方式。

使用 WebOTP API 分為三個部分:

  • 已正確註解的 <input> 標記
  • 網頁應用程式中的 JavaScript
  • 透過簡訊傳送的格式化訊息文字。

我會先說明 <input> 標記。

<input> 標記加上註解

WebOTP 本身不需要任何 HTML 註解即可運作,但為了確保跨瀏覽器相容性,強烈建議您在預期使用者輸入 OTP 的 <input> 標記中加入 autocomplete="one-time-code"

這樣一來,即使 Safari 14 以上版本不支援 WebOTP,當使用者收到符合「設定簡訊格式」所述格式的簡訊時,系統仍會建議使用者自動填寫 <input> 欄位。

HTML

<form>
  <input autocomplete="one-time-code" required/>
  <input type="submit">
</form>

使用 WebOTP API

由於 WebOTP 很簡單,只要複製並貼上下列程式碼即可。我會帶你瞭解目前的情況。

JavaScript

if ('OTPCredential' in window) {
  window.addEventListener('DOMContentLoaded', e => {
    const input = document.querySelector('input[autocomplete="one-time-code"]');
    if (!input) return;
    const ac = new AbortController();
    const form = input.closest('form');
    if (form) {
      form.addEventListener('submit', e => {
        ac.abort();
      });
    }
    navigator.credentials.get({
      otp: { transport:['sms'] },
      signal: ac.signal
    }).then(otp => {
      input.value = otp.code;
      if (form) form.submit();
    }).catch(err => {
      console.log(err);
    });
  });
}

特徵偵測

功能偵測方式與許多其他 API 相同。監聽 DOMContentLoaded 事件會等待 DOM 樹狀結構準備好查詢。

JavaScript

if ('OTPCredential' in window) {
  window.addEventListener('DOMContentLoaded', e => {
    const input = document.querySelector('input[autocomplete="one-time-code"]');
    if (!input) return;
    
    const form = input.closest('form');
    
  });
}

處理動態密碼

WebOTP API 本身相當簡單。使用 navigator.credentials.get() 取得一次性密碼。WebOTP 會為該方法新增 otp 選項。這個物件只有一個屬性:transport,其值必須是含有字串 'sms' 的陣列。

JavaScript

    …
    navigator.credentials.get({
      otp: { transport:['sms'] }
      …
    }).then(otp => {
    …

簡訊送達時,這會觸發瀏覽器的權限流程。如果獲得授權,傳回的 Promise 會使用 OTPCredential 物件解析。

取得的 OTPCredential 物件內容

{
  code: "123456" // Obtained OTP
  type: "otp"  // `type` is always "otp"
}

接著,將 OTP 值傳遞至 <input> 欄位。直接提交表單可省去使用者輕觸按鈕的步驟。

JavaScript

    
    navigator.credentials.get({
      otp: { transport:['sms'] }
      
    }).then(otp => {
      input.value = otp.code;
      if (form) form.submit();
    }).catch(err => {
      console.error(err);
    });
    

中止訊息

如果使用者手動輸入 OTP 並提交表單,您可以使用 options 物件中的 AbortController 執行個體取消 get() 呼叫

JavaScript

    
    const ac = new AbortController();
    
    if (form) {
      form.addEventListener('submit', e => {
        ac.abort();
      });
    }
    
    navigator.credentials.get({
      otp: { transport:['sms'] },
      signal: ac.signal
    }).then(otp => {
    

設定簡訊格式

API 本身看起來應該很簡單,但使用前請先瞭解幾件事。訊息必須在呼叫 navigator.credentials.get() 後傳送,且必須在呼叫 get() 的裝置上接收。最後,訊息必須符合下列格式:

  • 訊息開頭是人類可解讀的文字,其中包含 4 到 10 個字元的英數字串 (至少有一個數字),最後一行則保留給網址和 OTP。
  • 呼叫 API 的網站網址網域部分必須以 @ 開頭。
  • 網址必須包含井號 (「#」),後面接著 OTP。

例如:

Your OTP is: 123456.

@www.example.com #123456

以下是錯誤範例:

SMS 訊息格式錯誤的範例 為什麼無法運作
Here is your code for @example.com #123456 @ 預計會是最後一行的第一個字元。
Your code for @example.com is #123456 @ 預計會是最後一行的第一個字元。
Your verification code is 123456

@example.com\t#123456
@host#code 之間應有一個空格。
Your verification code is 123456

@example.com  #123456
@host#code 之間應有一個空格。
Your verification code is 123456

@ftp://example.com #123456
不得包含網址配置。
Your verification code is 123456

@https://example.com #123456
不得包含網址配置。
Your verification code is 123456

@example.com:8080 #123456
不得包含通訊埠。
Your verification code is 123456

@example.com/foobar #123456
不得包含路徑。
Your verification code is 123456

@example .com #123456
網域不得包含空白字元。
Your verification code is 123456

@domain-forbiden-chars-#%/:<>?@[] #123456
網域中不得有禁止使用的字元
@example.com #123456

Mambo Jumbo
@host#code 預計會是最後一行。
@example.com #123456

App hash #oudf08lkjsdf834
@host#code 預計會是最後一行。
Your verification code is 123456

@example.com 123456
缺少 #
Your verification code is 123456

example.com #123456
缺少 @
Hi mom, did you receive my last text 缺少 @#

示範

透過示範網頁嘗試各種訊息: https://chrome.dev/web-otp-demo

原始碼位於: https://github.com/GoogleChromeLabs/web-identity-demos/tree/main/web-otp-demo

從跨來源 iframe 使用 WebOTP

在跨來源 iframe 中輸入簡訊 OTP 通常用於確認付款,尤其是 3D Secure。WebOTP API 採用通用格式,支援跨來源 iframe,因此可傳送繫結至巢狀來源的 OTP。例如:

  • 使用者前往 shop.example,並使用信用卡購買一雙鞋。
  • 輸入信用卡號碼後,整合式付款服務供應商會在 iframe 中顯示 bank.example 的表單,要求使用者驗證電話號碼,以便快速結帳。
  • bank.example 會傳送內含一次性密碼的簡訊給使用者,讓他們輸入密碼來驗證身分。

如要從跨來源 iframe 使用 WebOTP API,您必須完成下列兩項操作:

  • 在簡訊中註解頂層框架來源和 iframe 來源。
  • 設定權限政策,允許跨來源 iframe 直接從使用者接收 OTP。
在 iframe 中運作的 WebOTP API。

您可以在 https://web-otp-iframe-demo.stackblitz.io 試用這個範例。

在簡訊中註解繫結來源

從 iframe 內呼叫 WebOTP API 時,簡訊必須包含頂層框架來源 (前面加上 @)、OTP (前面加上 #),以及 iframe 來源 (前面加上 @),並位於最後一行。

Your verification code is 123456

@shop.example #123456 @bank.exmple

設定權限政策

如要在跨來源 iframe 中使用 WebOTP,嵌入器必須透過 otp-credentials 權限政策授予這項 API 的存取權,以免發生非預期的行為。一般來說,達成這個目標的方法有兩種:

透過 HTTP 標頭:

Permissions-Policy: otp-credentials=(self "https://bank.example")

透過 iframe allow 屬性:

<iframe src="https://bank.example/…" allow="otp-credentials"></iframe>

如要查看更多指定權限政策的範例,請參閱這篇文章

在電腦上使用 WebOTP

在 Chrome 中,WebOTP 支援監聽其他裝置收到的簡訊,協助使用者在電腦上完成電話號碼驗證。

電腦版 WebOTP API。

使用者必須在電腦版 Chrome 和 Android 版 Chrome 上登入同一個 Google 帳戶,才能使用這項功能。

開發人員只需在電腦版網站上導入 WebOTP API,做法與行動版網站相同,但不需要任何特殊技巧。

詳情請參閱「使用 WebOTP API 在電腦上驗證電話號碼」。

常見問題

我傳送的訊息格式正確,但對話方塊未顯示。發生了什麼事?

測試 API 時,請注意以下事項:

  • 如果傳送者的電話號碼已列入接收者的聯絡人清單,由於底層 SMS User Consent API 的設計,系統不會觸發這項 API。
  • 如果 Android 裝置使用工作資料夾,且 WebOTP 無法運作,請改為在個人資料夾中安裝及使用 Chrome (也就是接收簡訊的資料夾)。

請返回格式頁面,確認簡訊格式是否正確。

這個 API 是否與不同瀏覽器相容?

Chromium 和 WebKit 針對 SMS 簡訊格式達成共識,Apple 也宣布 Safari 將支援這項格式,iOS 14 和 macOS Big Sur 均適用。雖然 Safari 不支援 WebOTP JavaScript API,但只要使用 autocomplete=["one-time-code"] 註解 input 元素,如果簡訊訊息符合格式,預設鍵盤就會自動建議您輸入 OTP。

使用簡訊驗證安全嗎?

簡訊動態密碼可用於首次提供電話號碼時驗證號碼,但由於電話號碼可能會遭到盜用或由電信業者回收,因此重新驗證時必須謹慎使用簡訊驗證。WebOTP 是方便的重新驗證和復原機制,但服務應搭配其他因素 (例如知識問題) 使用,或使用 Web Authentication API 進行嚴格驗證。

該到哪裡回報 Chrome 實作中的錯誤?

你是否發現 Chrome 實作方式有錯誤?

  • 前往 crbug.com 回報錯誤。請盡可能提供詳細資訊、重現問題的簡單操作說明,並將「元件」設為 Blink>WebOTP

如何協助這項功能?

您是否打算使用 WebOTP API?您的公開支持有助於我們決定功能的優先順序,並向其他瀏覽器供應商說明支援這些功能的重要性。使用主題標記 #WebOTP 傳送推文給 @ChromiumDev,告訴我們您在何處使用這項功能,以及使用方式。

資源