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

Fawaz Mohammad
Fawaz Mohammad
Nina Satragno
Nina Satragno

Web Authentication API(也称为 WebAuthn)支持服务器使用公钥加密(而不是密码)来注册用户和对用户进行身份验证。具体方法是实现这些服务器与强效身份验证器之间的集成。这些身份验证器可以是专用实体设备(例如安全密钥),也可以与平台集成(例如指纹读取器)。您可以访问 webauthn.guide,详细了解 WebAuthn。

开发者痛点

在此项目之前,WebAuthn 在 Chrome 上缺少原生调试支持。开发者在构建使用 WebAuth 的应用时,需要访问实体身份验证器。这尤其困难,原因有二:

  1. 身份验证器有很多不同的类型。为了调试各种配置和功能,开发者必须能够使用许多不同的身份验证器(有时价格昂贵)。

  2. 从设计上讲,实体身份验证器是安全的。因此,通常无法检查它们的状态。

为了让这项工作更轻松,我们在 Chrome DevTools 中添加了调试支持。

解决方案:新的 WebAuthn 标签页

WebAuthn DevTools 标签页可让开发者模拟这些身份验证器、自定义其功能并检查其状态,从而大大简化了 WebAuthn 调试。

新的 WebAuthn 标签页

实现

向 WebAuthn 添加调试支持是一个分为两个部分的过程。

分两个步骤完成

第 1 部分:向 Chrome DevTools 协议添加 WebAuthn 网域

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

CDP 可将 DevTools 前端与 Chromium 连接起来。在我们的示例中,我们利用 WebAuthn 网域操作来在 WebAuthn DevTools 标签页和 Chromium 的 WebAuthn 实现之间建立桥梁。

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

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

(阅读代码

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

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

虽然需要 3 个必要组件,但我们只需要关注其中的两个:模型视图。添加网域后,系统会自动生成第 3 个组件 - 代理。简而言之,代理是前端和 CDP 之间传递调用的层。

模型

模型是连接代理和视图的 JavaScript 层。在我们的示例中,模型非常简单。它会从视图获取命令,构建请求以便 CDP 使用,然后通过代理发送这些请求。这些请求通常是单向的,不会将任何信息发回给视图。

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

(阅读代码

视图

新的 WebAuthn 标签页

我们使用该视图提供开发者在访问 DevTools 时可以找到的界面。其中包含:

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

(阅读代码

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

工具栏

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

为何要这样做?请务必让用户明确切换环境,因为这样做会断开浏览器与真实身份验证器发现功能之间的连接。因此,当此功能处于开启状态时,系统不会识别已连接的物理身份验证器(例如指纹读取器)。

我们认为,使用显式切换开关可提供更好的用户体验,尤其是对于无意中进入 WebAuthn 标签页且不希望停用真实发现功能的用户。

添加“身份验证器”部分

添加“身份验证器”部分

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

用户点击 Add 后,这些选项会打包并发送到模型,后者会发出创建身份验证器的调用。完成后,前端将收到响应,然后修改界面以添加新创建的身份验证器。

“身份验证器”视图

“身份验证器”视图

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

身份验证器名称

身份验证器的名称可自定义,默认为“Authenticator”与其 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. 统计 DevTools 中 WebAuthn 标签页被打开的次数。此选项可能会导致过量统计,因为用户可能会打开标签页,但实际上并未使用。

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

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

摘要

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

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

下载预览渠道

不妨考虑将 Chrome Canary 版开发者版Beta 版用作默认开发浏览器。通过这些预览版渠道,您可以使用最新的 DevTools 功能、测试尖端的 Web 平台 API,并帮助您在用户发现问题之前发现网站上的问题!

与 Chrome DevTools 团队联系

您可以使用以下选项讨论与 DevTools 相关的新功能、更新或任何其他内容。