WebOTP API
WebOTP API 提供了一种简化的用户体验,让 Web 应用程序可以在用户使用手机号码作为登录因素时,验证手机号码是否属于用户。WebOTP 是 Credential Management API 的一个扩展。
验证通过一个两步过程完成。
- 应用客户端请求一个一次性密码(OTP),该 OTP 来自应用服务器发送的一个特殊格式的短信。
- 使用 JavaScript 将 OTP 输入到应用客户端的验证表单中,然后将其提交回服务器以验证是否与短信中原始发送的内容匹配。
概念与用法
电话号码通常被用作识别应用用户的标识。短信经常被用来验证该号码是否属于用户。短信通常包含一个 OTP,用户需要复制并粘贴到应用中的表单中,以验证他们拥有该号码。这是一种相当笨拙的用户体验。
OTP 的使用场景包括:
- 通过使用电话号码作为 多因素身份验证 系统的一部分来提高登录安全性。
- 验证敏感操作,例如付款。
WebOTP API 允许 Web 应用程序通过自动复制短信中的 OTP 并将其传递给应用程序来加快此验证过程,前提是用户已提供同意(大多数原生平台都有等效的 API)。
请注意,OTP 绑定到发送域。这是一个有用的安全限制,用于验证 OTP 是否来自正确的来源,这可以减轻日常重新验证期间网络钓鱼攻击的风险。
短信 OTP 的安全顾虑
短信 OTP 对于验证电话号码很有用,并且使用短信作为第二个因素肯定比没有第二个因素要好。在某些地区,电子邮件地址和身份验证器等其他标识符并未得到广泛使用,因此短信 OTP 非常普遍。
然而,短信的安全性不高。攻击者可以伪造短信并劫持一个人的电话号码。运营商可以在账户关闭后将电话号码回收给新用户。
因此,建议您如果可能,使用更强的身份验证形式,例如基于 Web Authentication API 的解决方案,包括密码和安全密钥或通行密钥。
WebOTP API 如何工作?
过程如下:
- 在需要电话号码验证的节点,应用客户端会要求用户在表单中输入他们的电话号码,然后将其提交给应用服务器。
- 应用客户端然后调用
navigator.credentials.get(),并附带一个otp选项,指定transport类型为"sms"。这会触发从底层系统请求 OTP,其来源是一个从应用服务器接收到的特殊格式的短信(包含 OTP 和应用的域名)。get()调用是基于Promise的,并等待接收短信。 - 应用服务器将短信发送到指定的电话号码。这必须在步骤 2 发生后立即进行。
- 当设备收到短信时,如果其中包含应用的域名,浏览器会询问用户是否同意检索/使用 OTP。例如,Chrome 会显示一个对话框,请求用户允许从短信中检索 OTP;其他浏览器可能处理方式不同。如果用户同意,
get()调用将以一个包含 OTP 的OTPCredential对象返回。 - 然后,您可以按需使用 OTP。典型用法是将其设置为应用客户端验证表单的值,然后提交表单,使过程尽可能无缝。
- 应用服务器然后验证回传给它的 OTP 是否与它最初在短信中发送的一致,如果一致,则完成过程(例如,登录用户)。
短信格式
典型的短信看起来像这样:
Your verification code is 123456. @www.example.com #123456
- 第一行和第二行空白行是可选的,用于人类可读性。
- 最后一行是强制性的。如果存在其他行,它必须是最后一行,并且必须由以下部分组成:
- 调用 API 的网站 URL 的域名部分,前面加上一个
@。 - 后面跟着一个空格。
- 后面跟着 OTP,前面加上一个井号(
#)。
- 调用 API 的网站 URL 的域名部分,前面加上一个
注意:提供的域名值不得包含 URL 方案、端口或其他未在上方显示的 URL 要素。
如果 get() 方法由嵌入在 <iframe> 中的第三方网站调用,则短信结构应为:
Your verification code is 123456. @top-level.example.com #123456 @embedded.com
在这种情况下,最后一行必须由以下部分组成:
- 顶级域的域名部分,前面加上一个
@。 - 后面跟着一个空格。
- 后面跟着 OTP,前面加上一个井号(
#)。 - 后面跟着一个空格。
- 后面跟着嵌入域的域名部分,前面加上一个
@。
控制对 API 的访问
WebOTP 的可用性可以使用 Permissions Policy 进行控制,该策略指定了 otp-credentials 指令。此指令的默认允许列表值为 "self",这意味着默认情况下,这些方法可以在顶层文档上下文中用于。
您可以指定一个指令,允许在特定的跨域域中使用 WebOTP(即,在 <iframe> 中),如下所示:
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()调用成功时返回;包含一个code属性,其中包含检索到的 OTP。
其他接口的扩展
CredentialsContainer.get(),otp选项-
使用
otp选项调用get()会指示用户代理尝试从底层系统的短信应用中检索 OTP。
示例
在此示例中,当收到短信并用户授予权限后,会返回一个带有 OTP 的 OTPCredential 对象。然后,此密码会预填充到验证表单字段中,并提交表单。
表单字段包含一个值为 one-time-code 的 autocomplete 属性。这对于 WebOTP API 的工作不是必需的,但值得包含。因此,即使 WebOTP API 在 Safari 中不受完全支持,当收到格式正确的短信时,Safari 也会提示用户使用 OTP 自动填充此字段。
<input type="text" autocomplete="one-time-code" inputmode="numeric" />
JavaScript 如下:
// Detect feature support via OTPCredential availability
if ("OTPCredential" in window) {
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 |
浏览器兼容性
加载中…
另见
- 来自 developer.chrome.com (2023) 的 使用 WebOTP 在 Web 上验证电话号码
- 使用 WebOTP API 在跨域 iframe 中填充 OTP 表单