使用通行密钥通过表单自动填充登录

创建一种利用通行密钥的登录体验,同时仍然兼容现有的密码用户。

通行密钥取代密码,使 Web 上的用户帐户更安全、更简单、更易于使用。但是,从基于密码的身份验证过渡到基于通行密钥的身份验证可能会使户体验变得复杂。使用表单自动填充来建议通行密钥可以帮助创建统一的体验。

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

通过通行密钥,用户只需使用指纹、面容或设备 PIN 码即可登录网站。

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

但是,从密码到基于通行密钥的身份验证的过渡可能具有挑战性。随着用户切换到通行密钥,仍然会有人使用密码,网站需要同时兼容这两种类型的用户。不应期望用户记住他们在哪些网站上切换到了通行密钥,因此要求用户预先选择使用哪种方法会造成不良的用户体验。

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

条件式界面

为了为通行密钥和密码用户构建高效的用户体验,您可以将通行密钥包含在自动填充建议中。这称为条件式界面,它是 WebAuthn 标准的一部分。

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

这样,用户可以使用现有表单登录您的网站,就像什么都没改变一样,但是如果他们有通行密钥,则可以获得通行密钥带来的额外安全优势

工作原理

要使用通行密钥进行身份验证,您需要使用 WebAuthn API

通行密钥身份验证流程中的四个组件是:用户

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

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

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

注释表单输入字段

如果需要,将 autocomplete 属性添加到用户名 input 字段。附加 usernamewebauthn 作为其令牌,以便建议通行密钥。

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

功能检测

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

  • 浏览器支持带有 PublicKeyCredential 的 WebAuthn。

浏览器支持

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

来源

浏览器支持

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

来源

// 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 将被解析,并将 PublicKeyCredential 对象返回到 RP 前端。

由于多种不同的原因,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 存储库中找到开源库

凭据通过匹配的公钥验证后,让用户登录。

请访问 服务器端通行密钥身份验证,查看更详细的说明

资源