我们如何构建 Chrome 开发者工具的 WebAuthn 标签页

法瓦兹·穆罕默德
Fawaz Mohammad
尼娜·萨特拉尼奥
Nina Satragno

Web Authentication API(也称为 WebAuthn)允许服务器使用公钥加密(而不是密码)来注册和验证用户。它通过在这些服务器与强有力的身份验证器之间实现集成来实现这一点。这些身份验证器可能是专用实体设备(例如安全密钥),也可能是集成了平台(例如指纹读取器)的设备。您可以点击 webauthn.guide,详细了解 WebAuthn。

开发者痛点

在该项目之前,WebAuthn 缺少 Chrome 上的原生调试支持。如果开发者构建使用 WebAuth 的应用,则需要拥有物理身份验证器的访问权限。这一操作尤为困难,原因有两个:

  1. 身份验证器具有多种不同风格。调试配置和功能的广度需要开发者使用许多不同且有时成本高昂的身份验证器。

  2. 从设计上来说,物理身份验证器是安全的。因此,通常无法检查其状态。

我们希望通过直接在 Chrome 开发者工具中添加调试支持来简化这一过程。

解决方案:新的 WebAuthn 标签页

WebAuthn“开发者工具”标签页允许开发者模拟这些身份验证器、自定义其功能以及检查其状态,从而简化 WebAuthn 的调试工作。

新的 WebAuthn 标签页

实施步骤

向 WebAuthn 添加调试支持涉及两个部分。

两部分式流程

第 1 部分:向 Chrome 开发者工具协议添加 WebAuthn 网域

首先,我们在 Chrome 开发者工具协议 (CDP) 中实现了一个新网域,该网域会连接到与 WebAuthn 后端进行通信的处理程序。

CDP 将开发者工具前端与 Chromium 连接起来。在本例中,我们利用 WebAuthn 域行为在 WebAuthn 开发者工具标签页和 Chromium 的 WebAuthn 实现之间架起桥梁。

WebAuthn 域允许启用和停用虚拟身份验证器环境,从而断开浏览器与真实的身份验证器发现之间的连接,并改为插入虚拟发现。

我们还向现有的虚拟验证器和虚拟发现接口(属于 Chromium WebAuthn 实现的一部分)公开了该域内的方法,这些方法充当薄层。这些方法包括添加和移除身份验证器,以及创建、获取和清除其已注册的凭据。

(阅读代码

第 2 部分:构建面向用户的标签页

其次,我们在 DevTools 前端构建了一个面向用户的标签页。标签页由视图和模型组成。自动生成的代理可将网域与标签页连接。

虽然需要 3 个必要组件,但我们只需要关注其中两个组件:模型和视图。modelmodel添加域名后,系统会自动生成第 3 个组件,即代理。简而言之,代理是指在前端和 CDP 之间承载通话的层。

模型

模型是连接代理和视图的 JavaScript 层。对我们来说,这个模型非常简单。它从视图接收命令,构建请求,使 CDP 能够使用这些请求,然后通过代理发送这些请求。这些请求通常是单向请求,不会将任何信息发送回视图。

不过,我们有时确实会回传模型的响应,以便为新创建的身份验证器提供 ID,或返回存储在现有身份验证器中的凭据。

(阅读代码

视图

新的 WebAuthn 标签页

我们使用该视图提供界面,以便开发者在访问开发者工具时找到该界面。其中包含:

  1. 用于启用虚拟身份验证器环境的工具栏。
  2. 用于添加身份验证器的部分。
  3. 用于显示已创建身份验证器的部分。

(阅读代码

用于启用虚拟身份验证器环境的工具栏

toolbar

由于大多数用户互动一次只与一个身份验证器(而非整个标签页)互动,因此我们在工具栏中提供的唯一功能是开启和关闭虚拟环境。

为什么需要这样做?用户必须明确切换环境,因为这样做会断开浏览器与真实的身份验证器发现的连接。因此,当此功能处于开启状态时,系统将无法识别已连接的实体身份验证器(例如指纹识别器)。

我们认为,设置明确的切换开关意味着更好的用户体验,特别是对于那些漫步到 WebAuthn 标签页的用户,不会预料到真实的发现功能会被停用。

添加“身份验证器”部分

添加“身份验证器”部分

启用虚拟身份验证器环境后,我们会向开发者提供内嵌表单,以便他们添加虚拟身份验证器。在此表单中,我们会提供一些自定义选项,以便用户决定身份验证器的协议和传输方法,以及身份验证器是否支持常驻密钥和用户验证。

当用户点击 Add 后,这些选项将捆绑并发送给模型,由该模型进行调用以创建身份验证器。完成上述操作后,前端将收到响应,然后修改界面以添加新创建的身份验证器。

身份验证器视图

身份验证器视图

每次模拟身份验证器时,我们都会向身份验证器视图中添加一个部分来表示该部分。每个身份验证器部分都包含名称、ID、配置选项、用于移除身份验证器或将其设为启用的按钮以及凭据表。

身份验证器名称

身份验证器的名称可自定义,默认采用“身份验证器”并将其 ID 的最后 5 个字符串联起来。最初,身份验证器的名称是完整 ID,不可更改。我们实现了可自定义的名称,以便用户可以根据身份验证器的功能、要模拟的实体身份验证器或者比 36 个字符的 ID 更易于消化的任何昵称来标记身份验证器。

凭据表

我们在每个身份验证器部分都添加了一个表格,以显示由身份验证器注册的所有凭据。每一行中都提供了相应凭据的相关信息以及用于移除或导出凭据的按钮。

目前,我们会通过轮询 CDP 来获取每个身份验证器的已注册凭据,从而收集相关信息来填充这些表格。未来,我们计划添加用于凭据创建的事件。

“活动”按钮

我们还为每个身份验证器部分添加了已启用单选按钮。当前设置为启用的身份验证器将唯一监听并注册凭据的身份验证器。否则,在向多个身份验证器提供凭据时注册凭据是不确定的,这会成为尝试使用这些身份验证器测试 WebAuthn 时的严重缺陷。

我们在虚拟身份验证器上使用 SetUserPresence 方法实现了活跃状态。SetUserPresence 方法用于设置对于指定的身份验证器,用户在线状态测试是否成功。如果我们关闭此功能,身份验证器将无法监听凭据。因此,通过确保最多为一个身份验证器(设置为活跃的身份验证器)开启该功能,并为所有其他身份验证器停用用户在线状态,我们就可以强制执行确定性行为。

在添加有效按钮时,我们遇到了一个有趣的挑战,那就是如何避免竞态条件。请考虑以下场景:

  1. 用户点击身份验证器 X 对应的有效单选按钮,从而向 CDP 发送请求,将 X 设为有效。选中 X 对应的有效单选按钮,并取消选中所有其他复选框。

  2. 紧接着,用户点击身份验证器 Y 对应的有效单选按钮,向 CDP 发送请求,将 Y 设为有效。Y 对应的有效单选按钮会被选中,所有其他状态(包括 X 的维度)均处于取消选中状态。

  3. 在后端,将 Y 设为活跃的调用已完成并解析。Y 已激活,所有其他身份验证器均未启用。

  4. 在后端,将 X 设为活动状态的调用已完成并解析。X 现已激活,所有其他身份验证器(包括 Y)均未启用。

现在,最终的情况如下所示:X 是有效身份验证器。但是,为 X 选择有效单选按钮。不是有效的身份验证器。不过,Y 对应的有效单选按钮处于选中状态。身份验证器的实际状态与前端状态不一致。显然,这是个问题。

我们的解决方案:在单选按钮和有效身份验证器之间建立伪双向通信。首先,我们在视图中维护一个变量 activeId,以跟踪当前处于活动状态的身份验证器的 ID。然后,我们会等待调用以将身份验证器设置为有效状态以返回,然后将 activeId 设置为该身份验证器的 ID。最后,我们将遍历每个身份验证器部分。如果该部分的 ID 等于 activeId,我们将按钮设置为选中状态。否则,我们将按钮设置为未选中状态。

如下所示:


 async _setActiveAuthenticator(authenticatorId) {
   await this._clearActiveAuthenticator();
   await this._model.setAutomaticPresenceSimulation(authenticatorId, true);
   this._activeId = authenticatorId;
   this._updateActiveButtons();
 }
 
 _updateActiveButtons() {
   const authenticators = this._authenticatorsView.getElementsByClassName('authenticator-section');
   Array.from(authenticators).forEach(authenticator => {
     authenticator.querySelector('input.dt-radio-button').checked =
         authenticator.getAttribute('data-authenticator-id') === this._activeId;
   });
 }
 
 async _clearActiveAuthenticator() {
   if (this._activeId) {
     await this._model.setAutomaticPresenceSimulation(this._activeId, false);
   }
   this._activeId = null;
 }

用量指标

我们希望跟踪此功能的使用情况。最初,我们提出了两个选项。

  1. 每次打开开发者工具中的“WebAuthn”标签页时进行计数。此选项可能会导致超量计数,因为可能会有人打开该标签页,但并未实际使用它。

  2. 跟踪工具栏中“启用虚拟身份验证器环境”复选框的切换次数。此选项还存在潜在的超量计数问题,因为部分选项可能会在同一会话中多次开启和关闭环境。

最终,我们决定采用后者,但通过检查相应环境是否已在会话中启用来限制计数。因此,无论开发者切换环境多少次,我们都只会将计数增加 1。这是因为每次重新打开该标签页时都会创建一个新会话,从而重置检查状态并允许指标再次递增。

摘要

感谢阅读!如果您对改进 WebAuthn 标签页有任何建议,请通过提交 bug 告诉我们。

如果您想详细了解 WebAuthn,可以参考以下资源:

下载预览渠道

不妨考虑将 Chrome Canary 版开发者版Beta 版用作默认开发浏览器。通过这些预览渠道,您可以使用最新的开发者工具功能,测试先进的网络平台 API,并先于用户发现您网站上的问题!

与 Chrome 开发者工具团队联系

使用以下选项讨论博文中的新功能和变化,或讨论与开发者工具有关的任何其他内容。

  • 请通过 crbug.com 向我们提交建议或反馈。
  • 使用开发者工具中的更多选项   了解详情   > Help > Report a DevTools issues,报告开发者工具问题。
  • 您可以前往 @ChromeDevTools 发 Twitter 微博。
  • 请在 YouTube 视频或“开发者工具提示”YouTube 视频中留言说明“开发者工具的新变化”。