有关短信动态密码表单的最佳做法

了解如何优化短信动态密码表单并改善用户体验。

Eiji Kitamura
北村英二

要求用户提供通过短信传送的动态密码(动态密码)是确认用户电话号码的常用方法。短信动态密码有以下几种用例:

  • 双重身份验证。除了用户名和密码之外,短信动态密码也可用作收到短信动态密码的用户的明确信号。
  • 电话号码验证。某些服务使用电话号码作为用户的主要标识符。在此类服务中,用户可以输入自己的手机号码和通过短信收到的动态密码来证明自己的身份。有时,将 PIN 码与 PIN 码结合使用就构成双重身份验证。
  • 帐号恢复。当用户失去对其帐号的访问权限时,需要一种方法来恢复帐号。常见的帐号恢复方法是向用户注册的电子邮件地址发送电子邮件,或向电话号码发送短信动态密码。
  • 付款确认:在付款系统中,某些银行或信用卡发卡机构会出于安全考虑,要求付款人额外进行身份验证。为此,我们通常会使用短信动态密码。

这篇博文将介绍为上述使用情形构建短信动态密码表单的最佳实践。

核对清单

为了利用短信动态密码提供最佳用户体验,请按以下步骤操作:

  • 结合使用 <input> 元素与:
    • type="text"
    • inputmode="numeric"
    • autocomplete="one-time-code"
  • @BOUND_DOMAIN #OTP_CODE 用作动态密码短信的最后一行。
  • 使用 WebOTP API

使用 <input> 元素

使用包含 <input> 元素的表单是您可以遵循的最重要的最佳实践,因为它适用于所有浏览器。即使这篇博文中的其他建议在某些浏览器中不起作用,用户仍然可以手动输入和提交动态密码。

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

下面介绍了一些提示,可确保输入字段充分利用浏览器功能。

type="text"

由于动态密码通常是 5 位或 6 位数字,因此对输入字段使用 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 及更高版本,但我们强烈建议您使用它,因为它可以轻松地改善这些平台上的短信动态密码体验。

`autocomplete="one-time-code"` 的实际运用。

autocomplete="one-time-code" 可以改善用户体验,但您还可以通过确保短信符合来源绑定的消息格式,实现更多目标。

设置短信文本的格式

根据通过短信传送的源绑定一次性验证码规范,提升动态密码输入用户体验。

格式规则很简单:完成短信发送时,使用接收者域名以 @ 开头,动态密码以 # 开头。

例如:

Your OTP is 123456

@web-otp.glitch.me #123456

对动态密码消息使用标准格式可以更轻松、更可靠地从消息中提取验证码。将动态密码与网站相关联后,将更难诱使用户向恶意网站提供代码。

使用这种格式有几个好处:

  • 动态密码将绑定到相应网域。如果用户所在的网域不是短信中指定的网域,则系统不会显示动态密码建议。 同时还能降低钓鱼式攻击和帐号被盗用的风险。
  • 现在,浏览器能够可靠地提取动态密码,而无需依赖于神秘、不稳定的启发式方法。

当网站使用 autocomplete="one-time-code" 时,搭载 iOS 14 或更高版本的 Safari 会根据上述规则建议动态密码。

此短信格式也适用于 Safari 以外的浏览器。Android 上的 Chrome、Opera 和 Vivaldi 也通过 WebOTP API 支持基于来源的一次性代码规则,但不支持通过 autocomplete="one-time-code"

使用 WebOTP API

WebOTP API 可让您访问短信中收到的动态密码。通过使用 otp 类型 (OTPCredential)(transport 包含 sms)调用 navigator.credentials.get(),网站将等待用户发送符合源绑定的一次性代码的短信并向其授予访问权限。将动态密码传递给 JavaScript 后,网站可以在表单中使用该密码,或直接将其 POST 到服务器。

navigator.credentials.get({
  otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);
WebOTP API 的实际运用。

如需详细了解如何使用 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);
    });
  });
}

照片由 Jason Leung 提供,由 Unsplash 提供。