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

通过发送短信要求用户提供动态密码 (OTP) 来确认其身份是一种常见做法。短信动态密码的一些应用场景包括:

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

请继续阅读,了解针对这些使用情形构建短信动态密码表单的最佳实践。

核对清单

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

  • <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"

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

请改用 type="text"。这不会将移动键盘变成仅显示数字,但没关系,因为下一个有关使用 inputmode="numeric" 的提示会完成这项任务。

inputmode="numeric"

使用 inputmode="numeric" 将移动键盘更改为仅显示数字。

有些网站会为动态密码输入字段使用 type="tel",因为当聚焦于该字段时,它还会将移动键盘切换为仅显示数字(包括 *#)。过去,在 inputmode="numeric" 未得到广泛支持时,曾使用过此 hack。由于 Firefox 开始支持 inputmode="numeric",因此无需使用语义上不正确的 type="tel" hack。

autocomplete="one-time-code"

借助 autocomplete 属性,开发者可以指定浏览器在提供自动补全帮助时需要具备的权限,并告知浏览器相应字段中预期包含的信息类型。

借助 autocomplete="one-time-code",每当用户在表单处于打开状态时收到短信,操作系统都会以启发式方式解析短信中的动态密码,键盘会建议用户输入该动态密码。它仅适用于 iOS、iPadOS 和 macOS 上的 Safari 12 及更高版本,但我们强烈建议您使用它,因为这是在这些平台上改善短信验证码体验的更好方式。

autocomplete="one-time-code" 正在执行操作。

autocomplete="one-time-code" 改善用户体验,但您还可以通过确保短信消息符合源绑定消息格式来进一步提升用户体验。

可选属性

可选属性包括:

如需了解更多建议,请参阅我们的登录表单最佳实践

设置短信文本的格式

通过与通过短信传送的源绑定的一次性验证码规范保持一致,提升输入 OTP 的用户体验。

从根本上讲,格式规则如下:在短信末尾添加以 @ 开头的接收者网域,以及以 # 开头的 OTP。

例如:

Your OTP is 123456

@web-otp.glitch.me #123456

OTP 消息的标准格式使提取操作更轻松、更可靠。 将 OTP 代码与网站相关联,可让用户更难被诱骗向恶意网站提供代码。

精确的格式设置规则

精确规则如下:

  • 消息开头是(可选)人类可读的文本,其中包含一个 4 到 10 个字符的字母数字字符串(至少包含一个数字),最后一行留给网址和 OTP。
  • 调用 API 的网站的网址的网域部分必须以 @ 开头。
  • 网址必须包含 #,后跟一次性密码。字符数不得超过 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,网站将等待符合来源绑定的一次性代码的短信送达,并等待用户授予访问权限。将 OTP 传递给 JavaScript 后,网站可以在表单中使用该 OTP,也可以直接将其 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);
    });
  });
}