WebOTP API
WebOTP API为 Web 应用程序提供了一种简化的用户体验,用于在使用电话号码作为登录因子时验证电话号码是否属于用户。WebOTP 是凭据管理 API的扩展。
验证通过两步过程完成
- 应用程序客户端请求一次性密码(OTP),该密码是从应用程序服务器发送的格式特殊的 SMS 消息中获取的。
- JavaScript 用于将 OTP 输入应用程序客户端上的验证表单,并将其提交回服务器以验证它是否与最初在 SMS 中发送的内容匹配。
概念和用法
电话号码通常用作识别应用程序用户的一种方式。通常使用 SMS 来验证号码是否属于用户。SMS 通常包含 OTP,用户需要将 OTP 复制粘贴到应用程序中的表单中以验证他们是否拥有该号码。这是一种有点笨拙的用户体验。
OTP 使用案例包括
- 通过使用电话号码作为额外因子来提高登录安全性(例如,用于双重身份验证 (2FA) 或多重身份验证 (MFA))。
- 验证敏感操作,例如付款。
WebOTP API 允许 Web 应用程序在用户提供同意后(大多数原生平台都有等效的 API)通过从 SMS 中复制 OTP 并将其自动传递给应用程序来加快此验证过程。
请注意,OTP 与发送域绑定。这是一个有用的安全约束,用于验证 OTP 是否来自正确的来源,这可以减轻日常重新认证期间的网络钓鱼攻击风险。
SMS OTP 的安全问题
SMS OTP 对于验证电话号码很有用,并且使用 SMS 进行第二因子验证肯定比没有第二因子验证要好。在某些地区,电子邮件地址和身份验证器等其他标识符的使用并不广泛,因此 SMS OTP 非常普遍。
但是,SMS 并不安全。攻击者可以伪造 SMS 并劫持用户的电话号码。运营商可以在帐户关闭后将电话号码回收给新用户。
因此,建议您尽可能使用更强大的身份验证形式,例如使用Web 身份验证 API 的基于密码和安全密钥或通行密钥的解决方案。
WebOTP API 如何工作?
该过程的工作原理如下
- 在需要电话号码验证的点,应用程序客户端将要求用户将他们的电话号码输入表单,然后将其提交给应用程序服务器。
- 然后,应用程序客户端使用
otp
选项调用navigator.credentials.get()
,指定transport
类型为"sms"
。这会触发从底层系统请求 OTP,该系统的来源将是来自应用程序服务器的格式特殊的 SMS 消息(包含 OTP 和应用程序的域)。get()
调用是Promise
驱动的,并等待 SMS 消息被接收。 - 应用程序服务器将 SMS 消息发送到指定的电话号码。这必须在步骤 2 发生后立即完成。
- 当 SMS 在设备上接收时,如果它包含应用程序的域,浏览器会询问用户是否同意检索/使用 OTP。例如,Chrome 会显示一个对话框,要求他们提供从 SMS 中检索 OTP 的权限;其他浏览器可能以不同的方式处理它。如果他们同意,
get()
调用将完成并返回一个包含 OTP 的OTPCredential
对象。 - 然后,您可以按任何您希望的方式使用 OTP。典型的用法是将其设置为应用程序客户端上验证表单的值,然后提交表单,使整个过程尽可能无缝。
- 然后,应用程序服务器将验证发送回它的 OTP 是否与它最初在 SMS 中发送的内容匹配,如果匹配,则完成该过程(例如,登录用户)。
SMS 消息格式
典型的 SMS 消息如下所示
Your verification code is 123456. @www.example.com #123456
- 第一行和第二空行是可选的,用于人类可读性。
- 最后一行是必需的。如果存在其他行,它必须是最后一行,并且必须包含
- 调用 API 的网站的 URL 的域名部分,前面是
@
。 - 后面是一个空格。
- 后面是 OTP,前面是井号 (
#
)。
- 调用 API 的网站的 URL 的域名部分,前面是
注意:提供的域名值不能包含 URL 模式、端口或上面未显示的其他 URL 功能。
如果 get()
方法由嵌入在<iframe>
中的第三方网站调用,则 SMS 结构应为
Your verification code is 123456. @top-level.example.com #123456 @embedded.com
在这种情况下,最后一行必须包含
- 顶级域的域名部分,前面是
@
。 - 后面是一个空格。
- 后面是 OTP,前面是井号 (
#
)。 - 后面是一个空格。
- 后面是嵌入域的域名部分,前面是
@
。
控制对 API 的访问
可以使用权限策略 控制 WebOTP 的可用性,指定otp-credentials
指令。此指令的默认允许列表值为 "self"
,这意味着默认情况下,这些方法可以在顶级文档上下文中使用。
您可以指定一个指令,允许在特定跨域域(即,在<iframe>
内)使用 WebOTP,例如
Permissions-Policy: otp-credentials=(self "https://embedded.com")
或者,您也可以在 <iframe>
上直接指定它,例如
<iframe src="https://embedded.com/..." allow="otp-credentials"> ... </iframe>
注意:如果策略禁止使用 WebOTP get()
,则由它返回的promise
将使用 SecurityError
DOMException
拒绝。
接口
OTPCredential
-
当 WebOTP
get()
调用完成时返回;包含一个包含检索到的 OTP 的code
属性。
对其他接口的扩展
CredentialsContainer.get()
,otp
选项-
使用
otp
选项调用get()
会指示用户代理尝试从底层系统的 SMS 应用程序检索 OTP。
示例
在此示例中,当 SMS 消息到达并且用户授予权限时,将返回一个包含 OTP 的OTPCredential
对象。然后,此密码将预先填充到验证表单字段中,并提交表单。
表单字段包含一个autocomplete
属性,其值为 one-time-code
。这对于 WebOTP API 的正常工作不是必需的,但值得包含。因此,即使 WebOTP API 在 Safari 中没有得到完全支持,Safari 也会在接收到格式正确的 SMS 时提示用户使用 OTP 自动填充此字段。
<input type="text" autocomplete="one-time-code" inputmode="numeric" />
JavaScript 如下所示
// Detect feature support via OTPCredential availability
if ("OTPCredential" in window) {
window.addEventListener("DOMContentLoaded", (e) => {
const input = document.querySelector('input[autocomplete="one-time-code"]');
if (!input) return;
// Set up an AbortController to use with the OTP request
const ac = new AbortController();
const form = input.closest("form");
if (form) {
// Abort the OTP request if the user attempts to submit the form manually
form.addEventListener("submit", (e) => {
ac.abort();
});
}
// Request the OTP via get()
navigator.credentials
.get({
otp: { transport: ["sms"] },
signal: ac.signal,
})
.then((otp) => {
// When the OTP is received by the app client, enter it into the form
// input and submit the form automatically
input.value = otp.code;
if (form) form.submit();
})
.catch((err) => {
console.error(err);
});
});
}
AbortController
的另一个好用途是在一定时间后取消 get()
请求
setTimeout(() => {
// abort after 30 seconds
ac.abort();
}, 30 * 1000);
如果用户分心或导航到其他地方,最好取消请求,这样他们就不会被呈现与他们不再相关的权限提示。
规范
规范 |
---|
WebOTP API |
另请参阅
- 使用 WebOTP 在网络上验证电话号码 在 developer.chrome.com 上(2023 年)
- 使用 WebOTP API 在跨域 iframe 中填充 OTP 表单