打造可利用通行密钥的登录体验,同时仍为现有密码用户提供服务。
通行密钥可取代密码,使网络上的用户帐号更安全、更简便、更易于使用。但是,从基于密码的身份验证转换为基于通行密钥的身份验证可能会使用户体验变得复杂。使用表单自动填充功能来建议通行密钥有助于打造统一的体验。
为什么要使用表单自动填充功能通过通行密钥登录?
借助通行密钥,用户只需使用指纹、面孔或设备 PIN 码即可登录网站。
理想情况下,没有密码用户,身份验证流程可以像单点登录按钮一样简单。当用户点按该按钮时,系统会弹出帐号选择器对话框,用户可以选择帐号,解锁屏幕进行验证并登录。
但是,从密码转换为基于通行密钥的身份验证可能极具挑战性。随着用户改用通行密钥,仍然有使用密码的用户,网站需要同时满足这两类用户的需求。不应要求用户自己记住他们在哪些网站上切换到了通行密钥,因此要求用户预先选择要使用的方法会导致用户体验不佳。
通行密钥也是一项新技术。对网站来说,要解释这些概念并确保用户能从容地使用它们,可能并非易事。我们可以依靠熟悉的用户体验来自动填充密码,从而解决这两个问题。
条件界面
若要为通行密钥和密码用户打造高效的用户体验,您可以在自动填充建议中包含通行密钥。这称为条件界面,属于 WebAuthn 标准的一部分。
当用户点按用户名输入字段时,系统会弹出自动填充建议对话框,其中会突出显示存储的通行密钥以及密码自动填充建议。然后,用户可以选择帐号并使用设备屏幕锁定功能登录。
这样,用户可以使用现有表单登录您的网站,就好像没有进行任何更改一样,但拥有通行密钥的额外安全优势(如果有)。
运作方式
如需使用通行密钥进行身份验证,请使用 WebAuthn API。
通行密钥身份验证流程的四个组成部分是:用户:
- 后端:此后端服务器保存着帐号数据库,该数据库存储有公钥和与通行密钥有关的其他元数据。
- 前端:与浏览器通信并向后端发送提取请求的前端。
- 浏览器:运行 JavaScript 的用户浏览器。
- 身份验证器:用户的身份验证器,负责创建并存储通行密钥。这可能与浏览器在同一设备上(例如使用 Windows Hello 时)或其他设备(例如手机)。
- 用户进入前端后,就会从后端请求使用通行密钥进行身份验证,并调用
navigator.credentials.get()
以启动使用通行密钥进行身份验证。这将返回一个Promise
。 - 当用户将光标放在登录字段中时,浏览器会显示一个包含通行密钥的密码自动填充对话框。如果用户选择通行密钥,系统会显示身份验证对话框。
- 用户使用设备的屏幕锁定功能验证身份后,系统会解析 promise 并将公钥凭据返回给前端。
- 前端将公钥凭据发送到后端。后端根据数据库中匹配帐号的公钥验证签名。如果成功,则表示用户已登录。
前提条件
iOS 16、iPadOS 16 和 macOS Ventura 上的 Safari 已公开支持条件式 WebAuthn 界面。Android、macOS 和 Windows 11 22H2 上的 Chrome 也支持此功能。
通过表单自动填充功能使用通行密钥进行身份验证
当用户想要登录时,您可以进行有条件的 WebAuthn get
调用,以指明可在自动填充建议中包含通行密钥。对 WebAuthn 的 navigator.credentials.get()
API 的条件调用不会显示界面,并且会一直等待,直到用户从自动填充建议中选择一个用于登录的帐号。如果用户选择通行密钥,浏览器将使用凭据解析 promise,而不是填写登录表单。然后页面会负责让用户登录。
注释表单输入字段
根据需要向用户名 input
字段添加 autocomplete
属性。附加 username
和 webauthn
作为其令牌,以便它提供通行密钥建议。
<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,并将 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
:包含创建时设置的用户 ID 的 ArrayBuffer。如果服务器需要选择其使用的 ID 值,或者后端希望避免基于凭据 ID 创建索引,则可以使用此值来代替凭据 ID。authenticatorAttachment
:如果此凭据来自本地设备,则返回platform
。否则为cross-platform
,特别是在用户使用手机登录时。如果用户需要使用手机登录,不妨考虑提示用户在本地设备上创建通行密钥。type
:此字段始终设置为"public-key"
。
如果您在 RP 服务器上使用库来处理公钥凭据对象,我们建议您在使用 base64url 对部分对象进行编码后,将整个对象发送到服务器。
验证签名
在服务器上收到公钥凭据时,请将其传递给 FIDO 库以处理对象。
使用 id
属性查找匹配的凭据 ID(如果您需要确定用户帐号,请使用 userHandle
属性,该属性是您在创建凭据时指定的 user.id
)。查看是否可以使用存储的公钥验证凭据的 signature
。为此,我们建议您使用服务器端库或解决方案,而不是自行编写代码。您可以在 awesome-webauth GitHub 代码库中找到开源库。
使用匹配的公钥验证凭据后,让用户登录。