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

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

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

  • 双重身份验证。除了用户名和密码之外,短信动态密码 (OTP) 还可以作为一个强有力的信号,表明账号归接收短信动态密码的用户所有。
  • 电话号码验证。某些服务会使用电话号码作为用户的主要标识符。在这种服务中,用户可以输入其电话号码和通过短信收到的 OTP 来证明其身份。有时,它会与 PIN 码组合使用,构成双重身份验证。
  • 账号恢复。当用户无法访问其账号时,需要有一种方法来恢复账号。向其注册的电子邮件地址发送电子邮件,或向其电话号码发送动态密码短信,是常见的账号恢复方法。
  • 付款确认:在付款系统中,出于安全考虑,部分银行或信用卡发卡机构会要求付款人进行额外的身份验证。短信 OTP 通常用于此用途。

本文介绍了针对上述用例构建短信动态密码表单的最佳实践。

核对清单

如需通过短信动态密码提供最佳用户体验,请按以下步骤操作:

  • <input> 元素与以下各项搭配使用:
    • type="text"
    • inputmode="numeric"
    • autocomplete="one-time-code"
  • @BOUND_DOMAIN #OTP_CODE 用作 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"

由于动态密码通常是五位或六位数字,因此使用 type="number" 作为输入字段可能看起来很直观,因为它会将移动键盘更改为仅限数字。不建议这样做,因为浏览器希望输入字段是可计数的数字,而不是一系列数字,这可能会导致意外行为。使用 type="number" 会在输入字段旁边显示向上键和向下键;按这些按钮可递增或递减数字,并可能会移除前面的零。

请改用 type="text"。这不会将移动设备键盘变成仅限数字的键盘,但没关系,因为下一个有关使用 inputmode="numeric" 的技巧会完成此任务。

inputmode="numeric"

使用 inputmode="numeric" 将移动设备键盘更改为仅限数字。

有些网站会为动态密码输入字段使用 type="tel",因为它还会在获得焦点时将移动键盘切换为仅限数字(包括 *#)的模式。在过去 inputmode="numeric" 不受广泛支持时,就曾使用过此 hack。自 Firefox 开始支持 inputmode="numeric" 以来,您无需使用语义错误的 type="tel" 黑客攻击。

autocomplete="one-time-code"

借助 autocomplete 属性,开发者可以指定浏览器提供自动补全帮助所需的权限,并告知浏览器该字段中预期的信息类型。

使用 autocomplete="one-time-code" 时,每当用户在表单打开时收到短信时,操作系统都会以启发法解析短信中的 OTP,并且键盘会建议用户输入 OTP。此功能仅适用于 iOS、iPadOS 和 macOS 上的 Safari 12 及更高版本,但我们强烈建议您使用此功能,因为它可以轻松改善这些平台上的短信 OTP 体验。

`autocomplete="one-time-code"` 的运作方式。

autocomplete="one-time-code" 可改善用户体验,但您还可以通过确保短信符合发送来源的消息格式来做更多事情。

设置短信文本格式

遵循通过短信发送的来源绑定型动态密码规范,提升输入动态密码的用户体验。

格式规则很简单:在短信末尾添加接收器网域(前面带有 @)和动态密码(前面带有 #)。

例如:

Your OTP is 123456

@web-otp.glitch.me #123456

使用标准格式发送动态密码邮件,可让您更轻松、更可靠地从中提取动态密码。将 OTP 代码与网站相关联后,攻击者就更难诱骗用户向恶意网站提供代码。

使用此格式有以下几点好处:

  • OTP 将绑定到该网域。如果用户位于短信中指定网域以外的网域,系统不会显示动态密码建议。这还可降低遭到钓鱼式攻击和潜在账号盗用风险。
  • 现在,浏览器无需依赖神秘且不稳定的启发词语,即可可靠地提取 OTP。

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

这种短信格式对 Safari 以外的浏览器也有益。Android 版 Chrome、Opera 和 Vivaldi 还支持使用 WebOTP API 实现与来源绑定的动态密码规则,但不通过 autocomplete="one-time-code"

使用 WebOTP API

WebOTP API 提供对短信中收到的 OTP 的访问权限。通过使用 otp 类型 (OTPCredential) 调用 navigator.credentials.get()(其中 transport 包含 sms),网站将等待符合来源绑定的一次性密码的短信传送,并由用户授予访问权限。将 OTP 传递给 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