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

Fawaz Mohammad
Fawaz Mohammad
Nina Satragno
Nina Satragno

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

开发者痛点

在此项目之前,WebAuthn 在 Chrome 上缺乏原生调试支持。一名开发者在构建使用 WebAuth 的应用时,需要访问物理身份验证器。这一过程尤为困难,原因有两个:

  1. 身份验证器有多种不同类型。调试各种配置和功能时,开发者需要能够使用许多不同的身份验证器,有时费用高昂。

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

我们希望直接在 Chrome DevTools 中添加调试支持,让这一过程变得更简单。

解决方案:新的 WebAuthn 标签页

WebAuthn 开发者工具标签页让开发者能够模拟这些身份验证器、自定义其功能并检查其状态,从而更轻松地调试 WebAuthn。

新的 WebAuthn 标签页

实现

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

流程分为两部分

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

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

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

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

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

(阅读代码

<ph type="x-smartling-placeholder">

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

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

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

模型

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

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

(阅读代码

视图

新的 WebAuthn 标签页

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

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

(阅读代码

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

工具栏

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

为什么有必要这样做?请务必让用户明确切换环境,因为这样做会断开浏览器与真实的身份验证器发现的连接。因此,开启此设置后,系统将无法识别已连接的物理身份验证器(例如指纹读取器)。

我们认为,显式切换意味着更好的用户体验,对于那些漫步进入 WebAuthn 标签页但没有预料到会被禁用的真正发现功能的用户来说,更是如此。

添加“身份验证器”部分

添加“身份验证器”部分

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

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

身份验证器视图

身份验证器视图

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

身份验证器名称

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

凭据表

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

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

“活动”按钮

我们还为每个身份验证器部分添加了一个 Active 单选按钮。当前设为有效状态的身份验证器将是唯一会监听和注册凭据的身份验证器。否则,在给定多个身份验证器的情况下,凭据的注册是不确定的,当您尝试使用这些身份验证器测试 WebAuthn 时,这将是一个严重的缺陷。

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

在添加主动按钮时,我们面临的一个有趣的挑战是避开竞态条件。请考虑以下场景:

  1. 用户点击身份验证器 X 的有效单选按钮,向 CDP 发送请求以将 X 设置为有效。X 对应的 Active 单选按钮已被选中,所有其他选项均处于取消选中状态。

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

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

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

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

我们的解决方案:在单选按钮和有效身份验证器之间建立伪双向通信。首先,我们在视图中维护一个变量 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 提交建议或反馈。
  • 使用更多选项报告开发者工具问题 展开 >帮助 >在开发者工具中报告开发者工具问题
  • 请发送电子邮件至 @ChromeDevTools
  • 请对我们的开发者工具新功能 YouTube 视频或开发者工具提示 YouTube 视频发表评论。