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

创建一种登录体验,既能利用通行密钥,又能适应现有的密码用户。

通行密钥可替代密码,让用户在网页上的账号更加安全、简单易用。不过,从基于密码的身份验证过渡到基于通行密钥的身份验证可能会使用户体验变得复杂。使用表单自动填充功能来建议通行密钥有助于打造统一的体验。

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

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

理想情况下,用户无需输入密码,身份验证流程可以像单个登录按钮一样简单。当用户点按该按钮时,系统会弹出一个账号选择器对话框,用户可以选择一个账号,解锁屏幕以进行验证和登录。

不过,从基于密码的身份验证改为基于通行密钥的身份验证可能并非易事。随着用户改用通行密钥,仍会有用户使用密码,因此网站需要同时适应这两类用户。用户自己不应记得自己已在哪些网站上改用通行密钥,因此要求用户预先选择要使用哪种方法会导致用户体验不佳。

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

条件界面

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

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

这样一来,用户就可以使用现有表单登录您的网站,就像没有任何变化一样,但如果他们有通行密钥,还可以获享通行密钥的额外安全优势

工作原理

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

通行密钥身份验证流程包含以下四个组成部分:用户:

  • 后端:您的后端服务器,用于存储账号数据库,其中存储了公钥和有关通行密钥的其他元数据。
  • 前端:与浏览器通信并向后端发送提取请求的前端。
  • 浏览器:运行 JavaScript 的用户浏览器。
  • 身份验证器:用户的身份验证器,用于创建和存储通行密钥。它可以与浏览器位于同一设备上(例如使用 Windows Hello 时),也可以位于其他设备(例如手机)上。
通行密钥身份验证示意图
  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.

来源

  • 浏览器支持使用 PublicKeyCredential.isConditionalMediationAvailable()WebAuthn 条件界面

浏览器支持

  • 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 前端。

承诺可能会因多种不同原因而被拒绝。您需要根据 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 代码库中找到开源库

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

如需更详细的说明,请参阅服务器端通行密钥身份验证

资源