通过表单自动填充功能实现通行密钥登录

打造使用通行密钥的登录体验,同时仍允许现有密码用户使用。

通行密钥取代了密码,使网络上的用户帐号更安全、更简单、更易于使用。但是,从基于密码的身份验证转换为基于通行密钥的身份验证可能会使用户体验变得复杂。使用表单自动填充功能来建议通行密钥有助于打造统一的体验。

为什么要使用表单自动填充功能通过通行密钥登录?

借助通行密钥,用户只需使用指纹、人脸或设备 PIN 码即可登录网站。

理想情况下,没有使用密码的用户,并且身份验证流程可以像单个登录按钮一样简单。当用户点按该按钮时,系统会弹出帐号选择器对话框,用户可以选择一个帐号、解锁屏幕进行验证并登录。

但是,从密码向基于通行密钥的身份验证的转换可能颇具挑战性。随着用户改用通行密钥,仍然会有使用密码和网站的用户需要同时满足这两类用户的需求。用户本身不应记住他们在哪些网站上已改用通行密钥,因此让用户预先选择要使用的方法会导致糟糕的用户体验。

通行密钥也是一项新技术。解释这些术语并确保用户习惯使用它们对网站来说可能是一大挑战。我们可以依靠熟悉的用户体验来自动填充密码来解决这两个问题。

条件界面

如需为通行密钥和密码用户打造高效的用户体验,您可以在自动填充建议中添加通行密钥。这称为条件界面,是 WebAuthn 标准的一部分。

用户只要点按用户名输入字段,就会弹出一个自动填充建议对话框,其中会突出显示存储的通行密钥以及密码自动填充建议。然后,用户可以选择一个帐号并使用设备屏幕锁定功能登录。

这样一来,用户便可以使用现有表单登录您的网站,就好像没有发生任何变化一样,但前提是他们有通行密钥的安全优势

运作方式

如需使用通行密钥进行身份验证,请使用 WebAuthn API

通行密钥身份验证流程的四个组成部分是:用户:

  • 后端:您的后端服务器,用于保存账号数据库,该数据库存储着公钥和通行密钥的其他元数据。
  • 前端:与浏览器通信并将提取请求发送到后端的前端。
  • 浏览器:运行 JavaScript 的用户浏览器。
  • 身份验证器:用于创建和存储通行密钥的用户身份验证器。它可能与浏览器位于同一设备上(例如,使用 Windows Hello 时),也可能位于另一台设备(例如手机)上。
通行密钥身份验证示意图
  1. 用户到达前端后,它会从后端请求质询以使用通行密钥进行身份验证,并调用 navigator.credentials.get() 以启动使用通行密钥进行身份验证。这将返回一个 Promise
  2. 当用户将光标置于登录字段中时,浏览器会显示一个包含通行密钥的密码自动填充对话框。如果用户选择通行密钥,系统会显示身份验证对话框。
  3. 用户使用设备的屏幕锁定功能验证身份后,系统会解析 promise,并向前端返回一个公钥凭据。
  4. 前端将公钥凭据发送到后端。后端根据数据库中匹配帐号的公钥验证签名。如果成功,则表示用户已登录。

前提条件

iOS 16、iPadOS 16 和 macOS Ventura 上的 Safari 公开支持条件式 WebAuthn 界面。Android、macOS 和 Windows 11 22H2 版 Chrome 也提供该 API。

通过表单自动填充功能使用通行密钥进行身份验证

当用户想要登录时,您可以进行条件式 WebAuthn get 调用,以指明通行密钥可能包含在自动填充建议中。对 WebAuthnnavigator.credentials.get() API 的条件调用不会显示界面,并且会一直处于待处理状态,直到用户从自动填充建议中选择用于登录的帐号。如果用户选择通行密钥,浏览器将使用凭据解析 promise,而不是填写登录表单。然后,由页面负责让用户登录。

为表单输入字段添加注释

如果需要,请在用户名 input 字段中添加 autocomplete 属性。将 usernamewebauthn 附加为令牌,以允许它提供通行密钥建议。

<input type="text" name="username" autocomplete="username webauthn" ...>

功能检测

在调用条件式 WebAuthn API 调用之前,请检查:

  • 浏览器支持 WebAuthn。
  • 浏览器支持 WebAuthn 条件界面。
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.​​isConditionalMediationAvailable) {  
  // Check if conditional mediation is available.  
  const isCMA = await PublicKeyCredential.​​isConditionalMediationAvailable();  
  if (isCMA) {  
    // Call WebAuthn authentication  
  }  
}  

从 RP 服务器提取质询

从 RP 服务器提取调用 navigator.credentials.get() 所需的质询:

  • challenge:在 ArrayBuffer 中由服务器生成的质询。这是防止重放攻击所必需的。请务必每次尝试登录都生成新的质询,并在一段时间后或登录尝试验证失败后忽略该质询。可以将其视为 CSRF 令牌。
  • allowCredentials:此身份验证可接受的一组凭据。传递一个空数组,即可让用户从浏览器显示的列表中选择可用的通行密钥。
  • userVerification:指示使用设备屏幕锁定功能进行用户验证是 "required""preferred" 还是 "discouraged"。默认值为 "preferred",表示身份验证器可以跳过用户验证。请将此属性设置为 "preferred" 或省略该属性。

使用 conditional 标志调用 WebAuthn API 以对用户进行身份验证

调用 navigator.credentials.get() 以开始等待用户身份验证。

// To abort a WebAuthn call, instantiate an `AbortController`.
const abortController = new AbortController();

const publicKeyCredentialRequestOptions = {
  // Server generated challenge
  challenge: ****,
  // The same RP ID as used during registration
  rpId: 'example.com',
};

const credential = await navigator.credentials.get({
  publicKey: publicKeyCredentialRequestOptions,
  signal: abortController.signal,
  // Specify 'conditional' to activate conditional UI
  mediation: 'conditional'
});
  • rpId:RP ID 是一个网域,网站可以指定其网域或可注册后缀。此值必须与创建通行密钥时使用的 rp.id 一致。

请务必指定 mediation: 'conditional',以使请求有条件。

将返回的公钥凭据发送到 RP 服务器

用户选择帐号并使用设备的屏幕锁定功能表示同意后,系统会解析 promise,并向 RP 前端返回 PublicKeyCredential 对象。

promise 可能会由于多种不同原因而被拒绝。您需要相应地处理错误,具体取决于 Error 对象的 name 属性:

  • NotAllowedError:用户已取消操作。
  • 其他例外情况:发生了意外情况。浏览器会向用户显示错误对话框。

公钥凭据对象包含以下属性:

  • id:经过身份验证的通行密钥凭据的 base64url 编码 ID。
  • rawId:凭据 ID 的 ArrayBuffer 版本。
  • response.clientDataJSON:客户端数据的 ArrayBuffer。此字段包含质询以及 RP 服务器需要验证的来源等信息。
  • response.authenticatorData:身份验证器数据的 ArrayBuffer。此字段包含 RP ID 等信息。
  • response.signature:签名的 ArrayBuffer。此值是凭据的核心,需要在服务器上进行验证。
  • response.userHandle:一个 ArrayBuffer,其中包含在创建时设置的用户 ID。如果服务器需要选择其使用的 ID 值,或者后端希望避免在凭据 ID 上创建索引,可以使用此值而不是凭据 ID。
  • authenticatorAttachment:如果凭据来自本地设备,则返回 platform。否则,cross-platform,特别是在用户使用手机登录时。如果用户需要使用手机登录,请考虑提示他们在本地设备上创建通行密钥
  • type:此字段始终设置为 "public-key"

如果您使用库在 RP 服务器上处理公钥凭据对象,我们建议您在使用 base64url 对部分对象进行编码后,将整个对象发送到服务器。

验证签名

在服务器上收到公钥凭据后,将其传递给 FIDO 库以处理对象。

使用 id 属性查找匹配的凭据 ID(如果您需要确定用户帐号,请使用 userHandle 属性,即您在创建凭据时指定的 user.id)。查看是否可以使用存储的公钥验证凭据的 signature。为此,我们建议您使用服务器端库或解决方案,而不是自行编写代码。您可以在 awesome-webauth GitHub 代码库中找到开源库

使用匹配的公钥验证凭据后,用户即可登录。

资源