使用 WebOTP API 在网页上验证电话号码

帮助用户通过短信收到动态密码

Eiji Kitamura
北村英二

什么是 WebOTP API?

如今,世界上大多数人都拥有移动设备,开发者通常使用电话号码作为其服务用户的标识符。

验证电话号码的方法有很多,但通过短信发送的随机生成的动态密码 (OTP) 是最常见的方式之一。将此代码发送回开发者的服务器演示了对电话号码的控制。

这一理念已应用于许多场景,可实现以下目标:

  • 用作用户标识符的电话号码。在注册新服务时,某些网站会要求您提供电话号码,而不是电子邮件地址,并将其用作帐号标识符。
  • 两步验证。登录时,网站会要求您在输入密码或其他验证凭据的同时通过短信发送一次性验证码,以进一步提高安全性。
  • 付款确认。在用户付款时,要求用户通过短信发送一次性验证码有助于验证用户的意图。

当前流程会给用户带来不便。在短信中查找动态密码,然后将其复制并粘贴到表单中的操作非常麻烦,而且会降低关键用户历程中的转化率。许多大型全球开发者长期以来希望能轻松满足此要求。Android 有一个可以做到这一点的 APIiOSSafari 也是如此。

WebOTP API 可让您的应用接收绑定到应用网域且格式特殊的消息。然后,您可以通过编程方式从短信中获取动态密码,并更轻松地验证用户的电话号码。

查看实际案例

假设某位用户想通过某个网站验证自己的电话号码。网站通过短信向用户发送短信,然后用户输入短信中的动态密码以验证电话号码的所有权。

如视频所示,借助 WebOTP API,用户只需点按一下即可轻松完成这些步骤。收到短信时,系统会弹出一个底部动作条,并提示用户验证电话号码。点击底部动作条上的验证按钮后,浏览器会将动态密码粘贴到表单中并提交表单,用户无需按继续

整个流程如下图所示。

WebOTP API 图表

亲自尝试演示。该应用不会要求您提供电话号码,也不会向您的设备发送短信,但您可以复制演示中显示的文字,以便从其他设备发送一个验证码。这种方法之所以有效,是因为使用 WebOTP API 时发送者是谁无关紧要的。

  1. 在 Android 设备上,使用 Chrome 84 或更高版本转到 https://web-otp.glitch.me
  2. 从另一部手机向您的手机发送以下短信。
Your OTP is: 123456.

@web-otp.glitch.me #12345

您收到短信后是否看到在输入区域输入验证码的提示? 这就是 WebOTP API 为用户服务的方式。

WebOTP API 的使用包括三个部分:

  • 正确添加了注解的 <input> 标记
  • Web 应用中的 JavaScript
  • 通过短信发送的格式化短信文字。

我先介绍 <input> 标记。

<input> 标记添加注解

WebOTP 本身无需任何 HTML 注释即可运行,但为了实现跨浏览器兼容性,我强烈建议您在预期用户输入动态密码时将 autocomplete="one-time-code" 添加到 <input> 标记中。

这样一来,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"
}

接下来,将动态密码值传递给 <input> 字段。直接提交表单将免去要求用户点按按钮的步骤。

JavaScript

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

取消邮件

如果用户手动输入动态密码并提交表单,您可以在 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 个字符的字母数字字符串,并且至少有一个数字,放在网址和动态密码的最后一行中。
  • 调用该 API 的网站网址的网域部分必须以 @ 开头。
  • 网址必须包含井号(“#”),后跟动态密码。

例如:

Your OTP is: 123456.

@www.example.com #123456

下面是反面示例:

格式错误的短信文字示例 此功能为何不起作用
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://web-otp.glitch.me

您还可以对其进行分支并创建您自己的版本:https://glitch.com/edit/#!/web-otp

使用跨源 iframe 中的 WebOTP

向跨源 iframe 输入短信动态密码通常用于确认付款,尤其是在使用 3D Secure 时。WebOTP API 具有支持跨源 iframe 的通用格式,因此会提供绑定到嵌套源的动态密码。例如:

  • 某用户访问 shop.example,使用信用卡购买了一双鞋。
  • 输入信用卡号后,集成付款服务机构会在 iframe 中显示 bank.example 的表单,要求用户验证其电话号码,以便快速结账。
  • bank.example 会向用户发送包含动态密码的短信,以便用户输入该密码来验证自己的身份。

若要在跨源 iframe 中使用 WebOTP API,您需要执行以下两项操作:

  • 在短信短信中为上帧来源和 iframe 来源添加注解。
  • 配置权限政策,以允许跨源 iframe 直接从用户接收动态密码。
iframe 中的 WebOTP API 的实际运用。

您可以访问 https://web-otp-iframe-demo.stackblitz.io 来试用演示版。

为短信的绑定源添加注解

从 iframe 中调用 WebOTP API 时,短信必须在最后一行包含顶帧源(以 @ 开头,后跟动态密码,以 # 开头, 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 在桌面设备上验证电话号码

FAQ

虽然我发送的是格式正确的邮件,但该对话框没有显示。出了什么问题?

测试该 API 时,需要注意以下几点:

  • 如果发送者的电话号码包含在接收者的联系人列表中,则由于底层 SMS User Consent API 的设计,系统不会触发此 API。
  • 如果您在 Android 设备上使用工作资料,但 WebOTP 不起作用,请尝试改为在您的个人资料(即您用来接收短信的个人资料)上安装并使用 Chrome。

请关注格式,查看您的短信格式是否正确。

此 API 是否兼容不同的浏览器?

Chromium 和 WebKit 就短信格式达成一致,并且 Apple 宣布 Safari 支持该格式(从 iOS 14 和 macOS Big Sur 开始)。虽然 Safari 不支持 WebOTP JavaScript API,但如果短信符合格式要求,默认键盘会自动建议您输入动态密码,方法是为 input 元素添加 autocomplete=["one-time-code"] 注解。

使用短信作为身份验证方式安全吗?

虽然短信动态密码对于在首次提供电话号码时验证电话号码很有用,但通过短信进行电话号码验证时,必须小心用于重新进行身份验证,因为运营商可能会劫持和回收电话号码。WebOTP 是一种方便的重新身份验证和恢复机制,但服务应将其与其他因素相结合(例如知识验证),或使用 Web Authentication API 实现强身份验证。

在哪里报告 Chrome 实现中的错误?

您是否发现了 Chrome 实现方面的错误?

  • 您可以在 https://new.crbug.com 上提交 bug。请提供尽可能详细的信息、简单的重现说明,并将 Components 设为 Blink>WebOTP

如何使用此功能?

您是否打算使用 WebOTP API?您公开提供的支持有助于我们确定功能的优先级,并向其他浏览器供应商说明支持这些功能的重要性。请使用 # 标签 #WebOTP@ChromiumDev 发送 Twitter 微博,并告知我们您在哪里以及如何使用该标签。

资源