WebOTP API

草案社区小组报告,

当前版本:
http://wicg.github.io/WebOTP
测试套件:
https://github.com/web-platform-tests/wpt/tree/master/sms
问题跟踪:
GitHub
编辑者:
(Google Inc.)

摘要

一个用于请求一次性密码以验证凭据(例如电话号码、电子邮件)的JavaScript API。

本文档的状态

本规范由Web Platform Incubator Community Group发布。 它不是W3C标准,也不在W3C标准路线图上。 请注意,根据W3C社区贡献者许可协议(CLA),有有限的选择退出权利,并且其他条件适用。 了解更多关于W3C社区和商业小组的信息。

logo

1. 简介

本节为非规范性内容。

许多网站需要在其身份验证流程中验证凭据(例如电话号码和电子邮件地址)。目前,它们依赖于将一次性密码(OTP)发送到这些通信渠道,以此作为所有权的证明。用户将一次性密码手动反馈给网络应用(通常通过复制/粘贴),这既繁琐又容易出错。

这是一个用于客户端的 JavaScript API 提案,允许网站请求一次性密码,以及一套传输特定的约定(我们从 SMS 开始,后续也可能添加其他方式),可用于与浏览器的协调。

1.1. 客户端 API

在这个提案中,网站能够调用浏览器 API 来请求来自特定传输方式(例如 SMS)的 OTP。

浏览器会中介接收 SMS 并将其传递给调用的网站(通常会询问用户的同意),因此该 API 会异步返回一个 promise。

let {code, type} = await navigator.credentials.get({
  otp: {
    transport: ["sms"]
  }
});

1.2. 服务器端 API

一旦调用客户端 API,网站的服务器就可以通过请求的传输机制向客户端发送 OTP。对于每种传输机制,都会设置相应的服务器端约定,以确保 OTP 安全且可编程地发送。

例如,对于 SMS,服务器应向客户端发送 绑定到源的一次性代码消息[sms-one-time-codes]

在以下的 绑定到源的一次性代码消息中,主机为 "example.com",代码为 "123456",解释性文本为 "您的验证码是 123456。\n"

"您的验证码是 123456.

@example.com #123456"

1.3. 特性检测

并非所有用户代理都需要在相同的时间点实现 WebOTP API,因此网站需要一种机制来检测 API 是否可用。

网站可以检查 OTPCredential 全局接口的存在:

if (!window.OTPCredential) {
  // 特性不可用
  return;
}

1.4. Web 组件

大多数情况下,OTP 验证主要依赖于:

我们希望其中一些框架可以开发该 API 的声明性版本,以便于其客户现有代码的部署。

Web 组件 Polyfills
<script src="sms-sdk.js"></script>

<form>
  <input is="one-time-code" required />
  <input type="submit" />
</form>

以下是框架如何使用 Web 组件来实现它的示例:

Web 组件 Polyfills
customElements.define("one-time-code",
  class extends HTMLInputElement {
    connectedCallback() {
      this.receive();
    }
    async receive() {
      let {code, type} = await navigator.credentials.get({
        otp: {
         transport: ["sms"]
        }
      });
      this.value = otp;
      this.form.submit();
    }
  }, {
    extends: "input"
});

1.5. 中止 API

许多现代网站在客户端处理导航。因此,如果用户从 OTP 流程导航到另一个流程,则需要取消请求,以免用户收到与当前无关的权限提示。

为此,可以传递一个中止控制器来取消请求:

const abort = new AbortController();

setTimeout(() => {
  // 两分钟后中止
  abort.abort();
}, 2 * 60 * 1000);
  
let {code, type} = await navigator.credentials.get({
  signal: abort.signal,
  otp: {
    transport: ["sms"]
  }
});

2. 客户端 API

网站调用 navigator.credentials.get({otp:..., ...}) 来获取 OTP。

navigator.credentials.get() 的算法会查找所有继承自 Credential 的接口,以实现 请求一个 Credential 的抽象操作。

在该操作中,找到继承自 CredentialOTPCredential, 然后调用 OTPCredential.[[CollectFromCredentialStore]]() 来收集任何不需要 用户介入 的可用 凭据,如果找不到唯一的凭据,就调用 OTPCredential.[[DiscoverFromExternalSource]]() 让用户选择凭据源并完成请求。

由于本规范要求使用 授权手势 来创建 OTP 凭据OTPCredential.[[CollectFromCredentialStore]]() 内部方法继承了 Credential.[[CollectFromCredentialStore]]() 的默认行为,即返回一个空集。

然后由 OTPCredential.[[DiscoverFromExternalSource]]() 来提供 OTP。

2.1. OTPCredential 接口

OTPCredential 接口继承自 Credential, 包含当获取新的一次性密码时返回给调用者的属性。

OTPCredential接口对象继承自 Credential[[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors) 的实现,并定义了它自己的 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 实现。

[Exposed=Window, SecureContext]
interface OTPCredential : Credential {
    readonly attribute DOMString code;
};
id

此属性继承自 Credential

[[type]]

OTPCredential 接口对象[[type]] 内部槽的值为字符串 "otp"。

code 类型为 DOMString,只读

检索到的一次性密码。

2.1.1. [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 方法

每次调用 navigator.credentials.get({otp:..., ...}) 时都会调用该方法,该方法负责在请求时返回 OTP(即当传入 options.otp 时)。

这个 内部方法接受三个参数:

origin

该参数是调用 get() 实现时由相关设置对象的 origin 确定的, 即 CredentialsContainer请求一个 Credential 的抽象操作。

options

该参数是一个 CredentialRequestOptions 对象,其 options.otp 成员包含一个 OTPCredentialRequestOptions 对象,用于指定要检索的 OTP 的所需属性。

sameOriginWithAncestors

该参数是一个布尔值,当且仅当调用者的 环境设置对象与其祖先同源时,该值为 true。如果调用者是跨源的,则为 false

注意:调用此 内部方法 表明其已被 权限策略允许,该策略在 [CREDENTIAL-MANAGEMENT-1] 级别进行评估。 参见 § 2.5 权限策略集成

注意: 该算法是同步的: Promise 的解决/拒绝由 navigator.credentials.get() 处理。

当调用此方法时,用户代理必须执行以下算法:

  1. 断言: options.otp存在的

  2. optionsoptions.otp 的值。

  3. callerOriginorigin。 如果 callerOrigin不透明的源,则返回一个名称为 "NotAllowedError" 的 DOMException, 并终止此算法。

  4. effectiveDomaincallerOrigin有效域。 如果 有效域 不是 有效域名,则返回一个名称为 "SecurityError" 的 DOMException, 并终止此算法。

    注意: 有效域 可能解析为一个 主机,其表示方式有多种, 例如 域名IPv4 地址IPv6 地址不透明主机空主机。 这里只允许使用主机的 域名 格式,这是为了简化操作,并认识到与基于 PKI 的安全性结合使用直接 IP 地址识别的各种问题。

  5. 如果 options.signal存在的,且其 中止标志 已设置为 true,则返回一个名称为 "AbortError" 的 DOMException, 并终止此算法。

  6. TODO(goto): 研究如何将传输算法与这里连接。

在上述过程中,用户代理应向用户显示一些 UI,以指导他们将 OTP 共享给源。

2.2. CredentialRequestOptions

为了支持通过 navigator.credentials.get() 获取 OTP,本文件扩展了 CredentialRequestOptions 字典,如下所示:

partial dictionary CredentialRequestOptions {
    OTPCredentialRequestOptions otp;
};
otp, 类型为 OTPCredentialRequestOptions

这个可选成员用于发起 WebOTP 请求。

2.3. OTPCredentialRequestOptions

OTPCredentialRequestOptions 字典为 navigator.credentials.get() 提供了检索 OTP 所需的数据。

dictionary OTPCredentialRequestOptions {
  sequence<OTPCredentialTransportType> transport = [];
};
transport, 类型为 sequence<OTPCredentialTransportType>,默认为 []

这个可选成员包含有关服务器如何接收 OTP 的提示。 值应为 OTPCredentialTransportType 的成员,但客户端平台必须忽略未知值。

2.4. OTPCredentialTransportType

enum OTPCredentialTransportType {
    "sms",
};
用户代理可以实现各种传输机制来允许 OTP 的检索。此枚举定义了用户代理如何与传输机制进行通信的提示。
sms

表示预期 OTP 将通过 SMS 到达。

2.5. 权限策略集成

本规范定义了一个由特征标识符 "otp-credentials" 确定的 权限控制特征。它的 默认允许列表是 'self'。 [Permissions-Policy]

Document权限策略 决定了该 文档 中的任何内容是否被允许成功调用 WebOTP API,即通过 navigator.credentials.get({otp: { transport: ["sms"]}})。 如果在任何文档中禁用,文档中的所有内容将不被允许使用上述方法:尝试这样做将返回一个错误。

2.6. iframe 元素中使用 WebOTP

当源匹配时,WebOTP API 可在内嵌框架中使用,但默认情况下在跨源 iframe 中被禁用。要覆盖此默认策略并指示允许跨源 iframe 调用 WebOTP API 的 [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) 方法,请在 allow 属性中包含 otp-credentials 特征标识符。

在嵌入上下文中使用 WebOTP API 的依赖方应查看 § 4.4 嵌入使用的可见性考虑,以了解关于 UI 改造及其可能的缓解措施。

3. 传输方式

我们预计会有多种不同的传输机制来接收 OTP,最常见的是通过 SMS、电子邮件和硬件设备。

每种传输机制都需要自己的约定来向浏览器提供 OTP。

在此草案中,我们将 API 表面留作扩展,以适应任意数量的传输方式。

3.1. SMS

最常用的 OTP 传输机制之一是通过 SMS 消息,允许开发者验证电话号码。通常,它们嵌入在 SMS 消息中,然后由用户复制粘贴。

[sms-one-time-codes] 定义了 绑定到源的一次性代码消息,一种通过 SMS 发送 OTP 并将其与源关联的格式。

4. 安全性

从安全角度来看,此 API 有两个方面需要考虑:

4.1. 可用性

此 API 仅在以下情况下可用:

此 API 也仅通过 https 或 localhost(用于开发目的)可用。我们不完全采用可信 URL 的概念,因为它涵盖的方案(例如 data://123)比我们期望的要多(我们的初步直觉是:(a) https 和 localhost 覆盖了大多数情况,(b) 用户需要明确知道它发送 SMS 的公共实体)。

4.2. 寻址

每种传输机制都负责确保浏览器有足够的信息将 OTP 适当地路由到预期的源。

例如,绑定到源的一次性代码消息明确识别了可以使用 OTP 的源。

代理必须强制执行寻址方案,以确保它能够适当地路由。

4.3. 篡改

没有内置的加密保证,确保此 API 返回的 OTP 没有被篡改。例如,攻击者可以向用户的手机发送包含任意源的 绑定到源的一次性代码消息,而代理会将其传递给请求调用。

您的验证码是: MUAHAHAHA

@example.com #MUAHAHAHA

调用者有责任:

4.4. 嵌入使用的可见性考虑

在嵌入上下文中简单使用 WebOTP,例如在 iframe 中使用,如 § 2.6 在 iframe 元素中使用 WebOTP 所述,可能会使用户面临 UI 重定向 攻击,也称为 "点击劫持(Clickjacking)"。这是指攻击者在依赖方预期的 UI 之上叠加其自己的 UI,企图诱骗用户对依赖方执行意外的操作。例如,使用这些技术,攻击者可能能够诱骗用户购买商品、转账等。

5. 隐私

从隐私的角度来看,最显著的考虑是用户代理应强制执行用户与网站之间的信息自愿交换。

具体来说,此 API 允许程序化验证用户的个人身份属性,例如电子邮件地址和电话号码。

最常被提出的攻击向量是有针对性的攻击:网站试图在其全部用户中找到一个非常特定的用户。在这种攻击中,如果不加以注意,网站可以使用此 API 尝试找到拥有特定电话号码的特定用户,方法是向所有/部分用户(取决于置信度)发送 绑定到源的一次性代码消息,并检测到接收时的状态。

值得注意的是,此 API 并不帮助获取个人信息,而是帮助验证个人信息。也就是说,此 API 有助于验证用户是否拥有特定的电话号码,但不会帮助首次获取电话号码(它假设网站已经可以访问它)。

尽管如此,这些属性的所有权验证仍然是有关用户的额外信息,用户代理应负责地处理它,通常通过在将 OTP 交回网站之前显示权限提示来进行。

6. 致谢

特别感谢 Steven Soneff、Ayu Ishii、Reilly Grant、Eiji Kitamura、Alex Russell、Owen Campbell-Moore、Joshua Bell、Ricky Mondello 和 Mike West 协助制定此提案。

特别感谢 Tab Atkins, Jr. 创建和维护了用于编写本规范的工具 Bikeshed,并为其提供了撰写建议。

一致性

文档约定

一致性要求由描述性断言和 RFC 2119 术语的组合来表达。 在本文件的规范部分中,“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”这些关键词应按照 RFC 2119 中的说明进行解释。 但是,为了可读性,这些词语在本规范中不以全大写字母出现。

本规范的所有文本都是规范性的,除非明确标记为非规范性的部分、示例和注释。[RFC2119]

本规范中的示例以“例如”开头,或通过 class="example" 与规范文本分开,例如:

这是一个信息性示例。

信息性注释以“注意”开头,并通过 class="note" 与规范文本分开,例如:

注意,这是一个信息性注释。

一致性算法

作为算法一部分的命令性措辞要求(例如“去除所有前导空格字符”或“返回 false 并终止这些步骤”),应根据引入算法时使用的关键词(“必须”、“应当”、“可以”等)来解释其含义。

以算法或具体步骤形式提出的一致性要求可以以任何方式实现,只要最终结果是等效的即可。 特别是,本规范中定义的算法旨在易于理解,并非为了性能优化。 鼓励实现者进行优化。

索引

本规范定义的术语

引用中定义的术语

参考文献

规范性引用

[CREDENTIAL-MANAGEMENT-1]
Mike West. Credential Management Level 1. 2019 年 1 月 17 日. WD. URL: https://www.w3.org/TR/credential-management-1/
[DOM]
Anne van Kesteren. DOM 标准. 现行标准. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch 标准. 现行标准. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; 等. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[Permissions-Policy]
Ian Clelland. Permissions Policy. 2020 年 7 月 16 日. WD. URL: https://www.w3.org/TR/permissions-policy-1/
[RFC2119]
S. Bradner. 用于在 RFC 中指示要求级别的关键字. 1997 年 3 月. 最佳现行做法. URL: https://tools.ietf.org/html/rfc2119
[SMS-ONE-TIME-CODES]
通过 SMS 发送的绑定到源的一次性代码. cg-draft. URL: https://wicg.github.io/sms-one-time-codes/
[WebIDL]
Boris Zbarsky. Web IDL. 2016 年 12 月 15 日. ED. URL: https://heycam.github.io/webidl/

信息性引用

[URL]
Anne van Kesteren. URL 标准. 现行标准. URL: https://url.spec.whatwg.org/

IDL 索引

[Exposed=Window, SecureContext]
interface OTPCredential : Credential {
    readonly attribute DOMString code;
};

partial dictionary CredentialRequestOptions {
    OTPCredentialRequestOptions otp;
};

dictionary OTPCredentialRequestOptions {
  sequence<OTPCredentialTransportType> transport = [];
};

enum OTPCredentialTransportType {
    "sms",
};