使用 WebOTP API 验证网络上的电话号码
帮助通过短信收到 OTP 的用户
Published on • Updated on
如果您想了解更多一般短信 OTP 表单最佳实践,包括 WebOTP API,请查看短信 OTP 表单最佳实践。
什么是 WebOTP API?
当今世界,大多数人都有一部移动设备,开发人员通常使用电话号码作为其服务用户的身份标识。
验证电话号码的方法有多种,但通过短信发送的随机生成的一次性密码 (OTP) 是最常见的方法之一。将此代码发回开发人员的服务器即表明对该电话号码拥有控制权。
这一想法已经部署在很多场景中来实现以下用途:
- **电话号码作为用户的身份标识。**注册新服务时,某些网站会要求提供电话号码而不是电子邮件地址,并将其用作帐户标识。
- **两步验证。**登录时,除密码或其他知识因素外,网站还会要求提供通过短信发送的一次性代码,以提高安全性。
- **支付确认。**当用户进行付款时,要求提供通过短信发送的一次性代码可以帮助核实用户的意图。
目前的流程给用户带来了一些麻烦。在短信消息中找到 OTP,然后将其复制并粘贴到表单中非常烦琐,会降低关键用户旅程的转化率。解决这一问题一直是许多全球知名开发商对网络的长期要求。Android 有一个 API 可以做到这一点。iOS 和 Safari 也能做到。
WebOTP API 允许您的应用程序接收绑定到您应用程序域的特殊格式的消息。因此,您可以通过编程方式从短信消息中获取 OTP,并更轻松地为用户验证电话号码。
攻击者可以伪造短信并窃取他人的电话号码。运营商也可以在帐户注销后回收电话号码出售给新用户。虽然短信 OTP 可在上述用例中验证电话号码,但我们建议使用其他更强的身份验证形式(例如多重因素和 Web 身份验证 API)为这些用户建立新会话。
实现方式
假设某位用户想要在网站上验证自己的电话号码。该网站通过短信向用户发送短信,用户输入短信中的 OTP 来验证电话号码的所有权。
如视频中所示,使用 WebOTP API 后,只需用户轻轻一按即可实现这些步骤。收到短信时,手机底部会弹出一个表单提示用户验证电话号码。单击底部表单上的验证按钮后,浏览器会将 OTP 粘贴到表单中,用户无需按继续即可提交表单。
整个过程如下图所示。

尝试一下演示中的操作。它不会询问您的电话号码或向您的设备发送短信,但您可以复制演示中显示的文本,从另一台设备发送短信。可以这样操作是因为在使用 WebOTP API 时发送方是谁并不重要。
- 在 Android 设备上的 Chrome 84 或更高版本中转到 https://web-otp.glitch.me。
- 从另一部手机向您的手机发送以下短信。
Your OTP is: 123456.
@web-otp.glitch.me #12345
收到短信后看到提醒您在输入区输入代码的提示了吗?这就是 WebOTP API 为用户工作的方式。
如果该对话框没有出现,请查看常见问题解答。
使用 WebOTP API 包括三个部分:
- 正确注释的
<input>
标签 - Web 应用程序中的 JavaScript
- 通过短信发送的固定格式消息文本。
首先来介绍 <input>
标签。
<input>
标签加注释
为 WebOTP 本身无需任何 HTML 注释即可工作,但为了跨浏览器兼容性,强烈建议您将 autocomplete="one-time-code"
添加到您希望用户输入 OTP 的 <input>
标签中。
这样可以使 Safari 14 或更高版本建议用户在收到具有设置短信格式中所述格式的短信时,使用 OTP 自动填充 <input>
字段,即使它不支持 WebOTP。
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 需要安全来源 (HTTPS)。HTTP 网站上的功能检测将失败。
处理 OTP
WebOTP API 本身很简单。使用 navigator.credentials.get()
获取 OTP。WebOTP 向该方法添加了一个新的 otp
选项。它只有一个属性:transport
,其值必须是一个包含字符串 'sms'
的数组。
JavaScript
…
navigator.credentials.get({
otp: { transport:['sms'] }
…
}).then(otp => {
…
当 SMS 消息到达时,会触发浏览器的权限流。如果授予权限,则返回的承诺将使用 OTPCredential
对象进行解析。
获取的 `OTPCredential` 对象的内容
{
code: "123456" // 获取的 OTP
type: "otp" // `type` 始终为 "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 个字符的字母数字字符串,其中至少有一个数字,最后一行用于 URL 和 OTP。
- 调用 API 的网站 URL 的域部分必须以
@
开头。 - URL 必须包含一个井号 ('
#
'),后跟 OTP。
例如:
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 | 不能包含 URL 方案。 |
Your verification code is 123456 @https://example.com #123456 | 不能包含 URL 方案。 |
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 中输入短信 OTP 通常用于支付确认,尤其是 3D Secure。WebOTP API 拥有支持跨域 iframe 的通用格式,提供绑定到嵌套源的 OTP。例如:
- 用户访问
shop.example
使用信用卡购买一双鞋。 - 输入信用卡号后,融合支付提供商会在 iframe 中显示一个来自
bank.example
的表单,要求用户验证其电话号码以便快速结账。 bank.example
向用户发送一条包含 OTP 的 短信,以便用户输入 OTP 来验证身份。
要在跨域 iframe 中使用 WebOTP API,您需要完成两项任务:
- 为短信文本消息中的 top-frame 原点和 iframe 原点添加注释。
- 配置权限策略,允许跨域 iframe 直接从用户处接收 OTP。
您可以尝试 https://web-otp-iframe-demo.stackblitz.io 中的演示。
为短信文本消息的 bound-origin 添加注释
从 iframe 内部调用 WebOTP API 时,短信文本消息必须包含以 @
开头的 top-frame 原点后跟以 #
开头的 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>
目前,Chrome 仅支持来自跨域 iframe 的 WebOTP API 调用,这些 iframe 在其祖先链中只有一个唯一来源。在以下场景中:
a.com
->b.com
a.com
->b.com
->b.com
a.com
->a.com
->b.com
a.com
->b.com
->c.com
支持在 b.com
中使用 WebOTP,但不支持在 c.com
中使用它。
请注意,由于缺乏需求和 UX 复杂性,也不支持以下场景。
a.com
->b.com
->a.com
(调用 WebOTP API)
常见问题解答
我发送了格式正确的消息,但对话框没有出现。这是怎么回事?
测试 API 时有几个注意事项:
- 如果发送方的电话号码在接收方的联系人列表中,则由于底层短信用户同意 API 的设计,不会触发此 API。
- 如果您在 Android 设备上使用工作号码而 WebOTP 不起作用,请尝试在您的个人号码(即您接收短信的号码)上安装和使用 Chrome。
再次查阅格式,查看您的短信格式是否正确。
这个 API 在不同浏览器之间兼容吗?
Chromium 和 WebKit 就 短信文本消息格式达成一致, Apple 宣布 Safari 从 iOS 14 和 macOS Big Sur 开始支持该 API。尽管 Safari 不支持 WebOTP JavaScript API,但通过使用 autocomplete=["one-time-code"]
注释 input
元素,如果短信消息符合格式,默认键盘会自动建议您输入 OTP。
使用短信作为身份验证方式是否安全?
虽然短信 OTP 可用于在首次提供电话号码时验证电话号码,但通过短信进行电话号码验证以重新进行身份验证时必须谨慎,因为电话号码可能会被运营商窃取和回收。WebOTP 是一种方便的重新认证和恢复机制,但服务应该将它与其他因素结合起来,例如知识挑战,或者使用 Web 身份验证 API 进行强身份验证。
在哪里报告 Chrome 实现中的错误?
您是否发现了 Chrome 实现的错误?
- 在 https://new.crbug.com 中提交错误。尽可能包括更多的详细信息,再现错误的简单说明,并将组件设置为
Blink>WebOTP
。
如何帮助此功能?
您打算使用 WebOTP API 吗?您的公开支持可以帮助我们设置各项功能的优先级,并向其他浏览器供应商展示支持它们的重要性。使用主题标签 #WebOTP
向 @ChromiumDev 发送推文,让我们知道您在何处以及如何使用它。
您可以在说明文档的常见问题解答部分找到更多问题。
Updated on • 改进文章