本页由自动翻译生成。请阅读英文原文 此处.

Passkeys 速查表. 面向 passkey 项目的实用指南、推广模式和 KPI。
在 2024 年,提供安全且简单的用户身份验证是数字公司的必备条件。通行密钥(Passkeys)作为新的登录标准,是满足这些需求的理想解决方案。然而,通行密钥为用户带来的增强的用户体验和安全性是有代价的——作为开发人员实现它们时。实现的难度源于通行密钥相对较新(对用户如此,对开发人员亦然),并且与基于密码的身份验证相比,它们的实现可能非常具有挑战性。实际上,通行密钥身份验证至少需要四个 API 端点,而密码身份验证只需要一个 API 端点。
在服务器端提供通行密钥身份验证的核心组件之一是 WebAuthn 服务器(绿色库部分)。有关 WebAuthn 服务器如何融入更广泛的企业技术栈集成的全面指南,请参阅我们的专门文章。
来源:Yubico
在这篇博文中,我们比较了几个 WebAuthn 服务器库/包/SDK,分析了差异,并为刚开始实现通行密钥的开发人员提供了建议。
为了更好地理解为什么一开始就需要一个 WebAuthn 服务器库,让我们看看如何实现通行密钥。原则上,有两种方法可以将通行密钥集成到网站和应用程序中:
虽然第三方通行密钥解决方案易于集成,通常可以节省大量的工程时间(特别是对于边缘情况、维护、恢复、后备和改善通行密钥用户体验而言),但一些开发人员只是更喜欢自己实现一切。
在实时 demo 中试用 passkeys。
让我们看看如何自己实现通行密钥(do-it-yourself)。在一个非常基本的设置中,需要一个注册(sign-up)和身份验证(login)的机制。这两个过程,也称为 WebAuthn 仪式(ceremonies),处理方式不同,即使整体流程遵循类似的模式:
PublicKeyCredentialCreationOptions 和 PublicKeyCredentialRequestOptions。这些 WebAuthn 参数中最重要的部分之一是 challenge(挑战)。然后,WebAuthn 参数被发回前端。由于每个注册 / 登录过程都涉及这些步骤,因此后端需要跟踪用户、通行密钥和注册 / 登录请求。
Igor Gjorgjioski
Head of Digital Channels & Platform Enablement, VicRoads
We hit 80% mobile passkey activation across 5M+ users without replacing our IDP.
See how VicRoads scaled passkeys to 5M+ users — alongside their existing IDP.
Read the case study如果您想深入了解通行密钥的工作方式以及简单实现是什么样子(不使用第三方通行密钥解决方案),您可以在这里阅读我们的博客文章。
在现实生活场景中,当您自己实现通行密钥时,请记住,不仅仅是提供必要的 API 端点和基本实现来注册和登录。最重要的是,您需要解决以下主题和用例:
尽管如此,对于基本的通行密钥实现,您只需要遵守 WebAuthn 标准。实现一个知名且受支持的 WebAuthn 服务器库通常就足够了。该库生成 WebAuthn 服务器参数并验证登录 challenge,实际上为您接管了密码学和最复杂的部分。
加入我们的 Passkeys Community,获取更新和支持。
所有被分析的 WebAuthn 服务器库都提供了提供通行密钥身份验证所需的功能。因此,我们特别关注以下标准:
分析了以下 WebAuthn 服务器库(按 2023 年 12 月 GitHub 星数降序排列):
查看实际有多少人在使用 passkeys。
authenticatorAttachment、residentKey、requireResidentKey 和 userVerification 变量,这些也在 W3C 页面上解释了)type UserModel = { id: string; username: string; currentChallenge?: string; }; /** * It is strongly advised that authenticators get their own DB * table, ideally with a foreign key to a specific UserModel. * * "SQL" tags below are suggestions for column data types and * how best to store data received during registration for use * in subsequent authentications. */ type Authenticator = { // SQL: Encode to base64url then store as `TEXT`. Index this column credentialID: Uint8Array; // SQL: Store raw bytes as `BYTEA`/`BLOB`/etc... credentialPublicKey: Uint8Array; // SQL: Consider `BIGINT` since some authenticators return atomic timestamps as counters counter: number; // SQL: `VARCHAR(32)` or similar, longest possible value is currently 12 characters // Ex: 'singleDevice' | 'multiDevice' credentialDeviceType: CredentialDeviceType; // SQL: `BOOL` or whatever similar type is supported credentialBackedUp: boolean; // SQL: `VARCHAR(255)` and store string array as a CSV string // Ex: ['usb' | 'ble' | 'nfc' | 'internal'] transports?: AuthenticatorTransport[]; };
public class StoredCredential { /// <summary> /// The Credential ID of the public key credential source. /// </summary> public byte[] Id { get; set; } /// <summary> /// The credential public key of the public key credential source. /// </summary> public byte[] PublicKey { get; set; } /// <summary> /// The latest value of the signature counter in the authenticator data from any ceremony using the public key credential source. /// </summary> public uint SignCount { get; set; } /// <summary> /// The value returned from getTransports() when the public key credential source was registered. /// </summary> public AuthenticatorTransport[] Transports { get; set; } /// <summary> /// The value of the BE flag when the public key credential source was created. /// </summary> public bool IsBackupEligible { get; set; } /// <summary> /// The latest value of the BS flag in the authenticator data from any ceremony using the public key credential source. /// </summary> public bool IsBackedUp { get; set; } /// <summary> /// The value of the attestationObject attribute when the public key credential source was registered. /// Storing this enables the Relying Party to reference the credent’al's attestation statement at a later time. /// </summary> public byte[] AttestationObject { get; set; } /// <summary> /// The value of the clientDataJSON attribute when the public key credential source was registered. /// Storing this in combination with the above attestationObject item enables the Relying Party to re-verify the attestation signature at a later time. /// </summary> public byte[] AttestationClientDataJson { get; set; } public List<byte[]> DevicePublicKeys { get; set; } public byte[] UserId { get; set; } public PublicKeyCredentialDescriptor Descriptor { get; set; } public byte[] UserHandle { get; set; } public string AttestationFormat { get; set; } public DateTimeOffset RegDate { get; set; } public Guid AaGuid { get; set; } }
<?php declare(strict_types=1); namespace App\Entity; use App\Repository\PublicKeyCredentialSourceRepository; use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Uid\AbstractUid; use Symfony\Component\Uid\Uuid; use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource; use Webauthn\TrustPath\TrustPath; #[ORM\Table(name: 'pk_credential_sources')] #[ORM\Entity(repositoryClass: PublicKeyCredentialSourceRepository::class)] class PublicKeyCredentialSource extends BasePublicKeyCredentialSource { #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] public readonly DateTimeImmutable $createdAt; #[ORM\Id] #[ORM\Column(type: Types::STRING, length: 255)] #[ORM\GeneratedValue(strategy: 'NONE')] private string $id; public function __construct( string $publicKeyCredentialId, string $type, array $transports, string $attestationType, TrustPath $trustPath, AbstractUid $aaguid, string $credentialPublicKey, string $userHandle, int $counter ) { $this->id = Uuid::v4()->toRfc4122(); $this->createdAt = new DateTimeImmutable(); parent::__construct($publicKeyCredentialId, $type, $transports, $attestationType, $trustPath, $aaguid, $credentialPublicKey, $userHandle, $counter); } public function getId(): string { return $this->id; } }
discouraged、preferred、required 的 UserVerificationRequirement 参数不同,webauthn4j 提供 verificationRequired 和 userPrecenseRequired 作为布尔变量下表提供了 WebAuthn 服务器库的概述:
订阅我们的 Passkeys Substack,获取最新消息。
由于大多数库同样强大并实现了 WebAuthn 标准,我们建议采用以下决策树:
go-webauthn 或 SimpleWebAuthn,因为它们的采用率最高、轻量级,并且也被许多商业和开源项目使用。如果您只是想总体上了解有关 WebAuthn 服务器的更多信息,而尚未有一个具体的项目,我们可以提出一些建议,因为这些库及其补充材料(如文档和示例实现)之间存在一些差异。因此,对于渴望开启通行密钥实现之旅的软件开发人员,我们建议选择以下实现:
py_webauthn 包提供了即时实现和现成可用的代码片段。只需一次方法调用,您就可以生成身份验证所需的选项。由于您不需要将库嵌入框架并配置它,因此它非常适合快速测试。SimpleWebauthn 包非常简单且对开发者友好。像 py_webauthn 一样,实现非常极简,但它还附带指导您完成身份验证过程的文档。然而,与 py_webauthn 相比,SimpleWebAuthn 没有带有一个无需修改即可运行的清晰示例。webauthn-framework 库以专注于通行密钥概念的广泛、结构化文档脱颖而出。与具有大量文档的其他库(如 webauthn4j)的主要区别在于文档的设计和生动性。如果想更深入地了解 WebAuthn 在服务器端是如何工作的,您可以阅读 WebAuthn RFC 中非常详细的“WebAuthn 信赖方操作(WebAuthn Relying Party Operations)”部分,其中详细说明了注册新凭据 (7.1)和验证身份验证断言 (7.2)所需实现的每个步骤。
评估您具有的特定通行密钥和 WebAuthn 需求。在这篇博文中,我们假设您只想支持通行密钥作为可发现凭据(discoverable credentials)。阅读有关 PublicKeyCredentialCreationOptions 和 PublicKeyCredentialRequestOptions 的信息,结合客户端的 navigator.credentials.create() 和 navigator.credentials.get() WebAuthn API 调用,在 WebAuthn 服务器 SDK 配置中为您的用例正确设置参数。
对于所有的 WebAuthn 服务器库,您都需要提供适当的数据库结构来持久化 / 访问以下信息:
对于某些库,有具体的建议和示例(如果我们认为它们有用,我们已在上面提供)。彻底了解需要将哪些 WebAuthn 字段存储在何处至关重要。特别注意识别您要用于用户 ID(user.id)的值。我们在这里有更详细的解释。还要考虑当用户可能删除通行密钥时会发生什么。除此之外,您可以选择性地限制某些身份验证器的使用。可以在此处找到与通行密钥相关的有效身份验证器列表。如果您还想支持并检查安全密钥(security keys)的证明(attestations),这完全是另一回事了。您可以在此处找到更多信息。
识别您的用户将在哪些设备上使用通行密钥和后备身份验证方法。如果您不确定您的用户使用哪些设备、浏览器和操作系统,请查看 State of Passkeys 获取跨平台、浏览器和操作系统的最新通行密钥就绪情况数据。如果您对某些设备的通行密钥采用率和通行密钥就绪份额有具体问题,请随时联系我们。我们很乐意为您提供进一步的见解,并在此主题上帮助您(另请参阅我们关于通行密钥就绪情况的最新博文)。从可观测性的角度来看,将客户端 WebAuthn 故障和服务器验证拒绝作为不同的数据流保留;对于客户端存储桶(bucket)定义,请使用 WebAuthn 错误。此外,您应该记住,对于 Windows 10 和 Linux,您需要提出专门的解决方案,因为这些操作系统提供的通行密钥支持最少(如果有的话)。
查看实际有多少人在使用 passkeys。
目前几乎对于所有语言或框架都有一个成熟的 WebAuthn 服务器库。比较不同语言的库并没有显示出某些实现的明显优势。相反,您应该使用您最熟悉的框架 / 编程语言。或者,如果您不想自己实现 WebAuthn 并处理所有随之而来的麻烦,您可以尝试像 Corbado 这样专门的、预构建的通行密钥身份验证解决方案。作为一个以通行密钥为中心的一体化身份验证解决方案,它带有很棒的通行密钥情报、会话管理以及后备身份验证方法,因此您可以专注于开发您的产品,而无需担心身份验证。您可以在此注册不限用户数的免费试用。
Corbado 是面向大规模运行 consumer 身份验证的 CIAM 团队的Authentication Intelligence Platform。我们让你看到 IDP 日志和通用 analytics 工具看不到的内容:哪些设备、操作系统版本、浏览器和 credential manager 支持 passkey,为什么注册没有转化为登录,WebAuthn 流程在哪里失败,以及什么时候操作系统或浏览器更新会悄悄破坏登录 — 而且无需替换 Okta、Auth0、Ping、Cognito 或你自有的 IDP。两款产品:Corbado Observe 提供 针对 passkey 及任何其他登录方式的 observability。Corbado Connect 提供 内置 analytics 的 managed passkey(与你的 IDP 并存)。VicRoads 通过 Corbado 为 500 万以上用户运行 passkey(passkey 激活率 +80%)。 与 Passkey 专家交谈 →
首先检查是否存在适用于您特定框架的库,然后是适用于您的编程语言的库。由于所有列出的库都同等地实现了 WebAuthn 标准,选择应该优先考虑对您现有技术栈的熟悉程度,而不是库之间的功能差异。
至少必须持久化凭证、用户、挑战和身份验证器。密切注意您分配为用户 ID(userHandle)的值,并为用户可能从其设备中删除通行密钥的情况做好计划。
WebAuthn 服务器库处理最复杂的密码学操作:生成 PublicKeyCredentialCreationOptions 和 PublicKeyCredentialRequestOptions 参数以及验证签名的挑战。从头开始正确地执行此操作比使用已经过测试和审计的符合 FIDO 标准的库要困难得多。
Windows 10 和 Linux 提供的通行密钥支持最少,因此需要为这些平台上的用户提供专用的后备解决方案。将客户端 WebAuthn 故障和服务器验证拒绝作为独立的流进行监控,有助于在生产环境中识别特定于操作系统的问题。
相关文章
目录