帮助用户使用通过短信接收的动态密码 (OTP)
WebOTP API 是什么?
如今,世界上大多数人都拥有移动设备,开发者通常会使用电话号码作为其服务用户的标识符。
验证电话号码的方法有很多种,但通过短信发送随机生成的动态密码 (OTP) 是最常见的方法之一。将此代码发送回开发者的服务器,表明您对电话号码拥有控制权。
此想法已在许多场景中部署,以实现以下目标:
- 电话号码作为用户的标识符。注册新服务时,有些网站会要求提供电话号码(而非电子邮件地址),并将其用作账号标识符。
- 两步验证。在登录时,网站会要求您在输入密码或其他知识凭据的基础上,再输入通过短信发送的动态验证码,以提高安全性。
- 付款确认。当用户进行付款时,要求用户输入通过短信发送的动态验证码有助于验证用户的意图。
当前流程会给用户带来不便。在短信中查找动态密码,然后将其复制并粘贴到表单中,这很麻烦,会降低关键用户体验历程中的转化率。许多全球大型开发者一直希望 Web 平台能够简化此流程。Android 有一个 API 可用于此目的。iOS 和 Safari 也是如此。
借助 WebOTP API,您的应用可以接收绑定到应用网域的特殊格式的消息。这样,您就可以通过编程方式从短信中获取动态密码,并更轻松地为用户验证电话号码。
查看实际案例
假设某个用户想通过某个网站验证其电话号码,网站通过短信向用户发送短信,用户输入短信中的动态密码以验证电话号码的所有权。
借助 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 注解即可正常运行,但为了实现跨浏览器兼容性,我们强烈建议您在用户预计输入 OTP 的 <input>
标记中添加 autocomplete="one-time-code"
。
这样一来,当用户收到采用短信格式中所述格式的短信时,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 本身非常简单。使用 navigator.credentials.get()
获取 OTP。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"
}
接下来,将 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 个字符的字母数字字符串(其中至少包含一个数字),最后一个行留作网址和 OTP 的用途。
- 调用 API 的网站网址的域名部分必须位于
@
之前。 - 网址必须包含井号 (
#
) 和 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 |
不得包含网址架构。 |
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 中输入短信 OTP 通常用于付款确认,尤其是在使用 3D 安全验证时。WebOTP API 采用常见格式来支持跨源 iframe,可提供绑定到嵌套源的 OTP。例如:
- 用户访问
shop.example
,使用信用卡购买一双鞋子。 - 输入信用卡号后,集成的付款服务提供商会在 iframe 中显示
bank.example
中的表单,要求用户验证其电话号码以便快速结账。 bank.example
向用户发送包含 OTP 的短信,以便用户输入该 OTP 来验证其身份。
如需在跨源 iframe 中使用 WebOTP API,您需要执行以下两项操作:
- 为短信文本消息中的顶级框架来源和 iframe 来源添加注释。
- 配置权限政策,以允许跨源 iframe 直接从用户接收 OTP。
您可以访问 https://web-otp-iframe-demo.stackblitz.io 试用演示版。
为短信添加绑定来源注释
从 iframe 内调用 WebOTP API 时,短信文本消息必须包含前面带有 @
的顶级框架源,后跟前面带有 #
的 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>
请参阅有关如何指定权限政策的更多示例 。
在桌面设备上使用 WebOTP
在 Chrome 中,WebOTP 支持监听其他设备上收到的短信,以协助用户在桌面设备上完成电话号码验证。
若要使用此功能,用户需要在桌面版 Chrome 和 Android 版 Chrome 中登录同一 Google 账号。
所有开发者只需在其桌面版网站上实现 WebOTP API,就像在移动版网站上实现一样,无需任何特殊技巧。
如需了解详情,请参阅使用 WebOTP API 在桌面设备上验证电话号码。
常见问题解答
我发送的邮件格式正确,但对话框却没有显示。哪里出问题了?
测试该 API 时,需要注意以下几点:
- 如果发件人的电话号码包含在收件人的联系人名单中,则由于底层 SMS User Consent API 的设计,系统不会触发此 API。
- 如果您在 Android 设备上使用的是工作资料,并且 WebOTP 不起作用,请尝试改为在个人资料(即您接收短信的资料)中安装并使用 Chrome。
请再次查看格式,看看短信的格式是否正确。
此 API 是否在不同浏览器之间兼容?
Chromium 和 WebKit 就短信格式达成了一致,Apple 宣布 Safari 从 iOS 14 和 macOS Big Sur 开始支持该格式。虽然 Safari 不支持 WebOTP JavaScript API,但通过使用 autocomplete=["one-time-code"]
为 input
元素添加注解,如果短信符合格式,默认键盘会自动建议您输入 OTP。
使用短信进行身份验证是否安全?
虽然短信动态密码在首次提供电话号码时非常有用,但在重新进行身份验证时,必须谨慎使用通过短信进行的电话号码验证,因为电话号码可能会被运营商盗用和回收。WebOTP 是一种便捷的重新身份验证和恢复机制,但服务应将其与其他因素(例如知识问答)结合使用,或使用 Web Authentication API 进行强制身份验证。
在哪里可以报告 Chrome 实现中的 bug?
您是否发现了 Chrome 实现中的 bug?
- 请访问 crbug.com 提交 bug。请尽可能提供详细信息,提供简单的重现说明,并将组件设置为
Blink>WebOTP
。
如何帮助改进此功能?
您打算使用 WebOTP API 吗?您的公开支持有助于我们确定功能的优先级,并向其他浏览器供应商表明支持这些功能的重要性。使用 #WebOTP
标签向 @ChromiumDev 发送推文,告诉我们您在哪里以及如何使用该工具。