我正在考虑在我的web应用程序中添加对WebAuthn/passkeys的支持,但事实上,你需要有单独的注册和登录流,并且仍然需要用户名,这对我来说几乎是不可能的。我真的很想知道我是否遗漏了什么,或者这是否可以以某种方式让用户看不见。
例如,我目前提供通过电子邮件登录:用户只需输入他们的电子邮件地址,就会收到登录链接。无论他们是否是现有用户;如果是一个新的电子邮件地址,我可以简单地创建一个新帐户。对于用户来说,这是完全不可见的,他们不必在";寄存器";以及";登录";按钮,他们不必尝试和记住他们是否已经有一个帐户。只需输入您的电子邮件地址即可。
我还提供了与苹果的登录,它有相同的流程:我从苹果的令牌中获得一个唯一的苹果用户ID(主题),检查数据库中是否有具有该ID的用户,并将该用户登录。如果没有,我会创建一个新用户,存储该ID,并将其登录;寄存器";以及";"登录";,他们根本不需要输入任何个人信息。
但是使用WebAuthn,您必须使用用户名创建或获取凭据。因此,我必须在网站上显示一个用户名输入字段,将其发送到服务器,服务器检查该用户是否存在,并在此基础上,网站可以创建或获取凭据。但是用户名很糟糕,一旦你有足够的用户,每个人都必须选择一个唯一的用户名,这总是令人沮丧的。因此,我可以要求提供电子邮件地址,但我想尽可能少地存储个人身份信息(是的,我提供通过电子邮件登录的方式,但这就是为什么我想提供WebAuthn作为替代选项)。
那么,如何简化这一流程呢?有没有办法绕过用户名要求,浏览器只需询问验证器是否有这个网站的公钥,如果没有,就创建它;使用密钥登录";按钮,而无需询问用户名(或电子邮件地址)。
虽然从技术上讲,可发现的WebAuthn凭据(密钥)不需要用户名,但用户仍然需要一个可识别的字符串来识别凭据。
例如,如果用户在一个网站上有两个不同的帐户,您不希望他们在从密钥凭据列表中选择帐户时看到随机字符串,因为这对用户选择正确的凭据没有帮助。
那么,如何简化呢?有没有办法绕过用户名要求,浏览器只需询问验证器是否有这个网站的公钥,如果没有,就创建它;使用密钥登录";按钮,而无需询问用户名(或电子邮件地址)。
如果您想完全承诺;usernameless";使用WebAuthn,用户在创建帐户之前不需要提供任何信息,然后只需弥补user
中传递给navigator.credentials.get()
:的三个必需值
const credential = await navigator.credentials.create({
publicKey: {
// ...
user: {
id: randomUserIDBytesGeneratedByServer,
name: bytesToString(randomUserIDBytesGeneratedByServer),
displayName: bytesToString(randomUserIDBytesGeneratedByServer),
},
authenticatorSelection: {
residentKey: 'required',
userVerification: true
},
},
});
randomUserIDBytesGeneratedByServer
是在新用户单击"注册"按钮时生成的一些随机、唯一的ID。为name
和displayName
提供某种编码值(我使用bytesToString()
作为将这些随机字节编码为某种字符串的简写,例如base64url)。
将可发现的credential
发送回您的服务器,对其进行验证,并在成功完成后在您的数据库中创建新的";CCD_ 8";将randomBytesGeneratedByServer
分配为其ID的帐户。请确保稍后还将credential.id
与另一个表中的randomUserIDBytesGeneratedByServer
关联。
当用户返回登录时,让他们单击Authenticate按钮以使用空的allowCredentials
数组触发navigator.credentials.get()
。这将使用户能够选择先前注册的可发现凭据:
const credential = await navigator.credentials.get({
publicKey: {
// ...
allowCredentials: [],
},
});
在成功验证了对.get()
的调用返回的响应后,您可以通过从用于记住credential.id
属于哪个用户的表中获取randomUserIDBytesGeneratedByServer
来查找User
的ID。此时,您可以使用任何适合您的设置的方式为用户建立经过身份验证的会话。
这听起来可能有点极端,但事实上,WebAuthn并没有阻止您在不收集任何类型的用户身份信息的情况下请求凭据并允许身份验证。
不可发现的凭据要求依赖方网站提供以前注册的凭据句柄的列表。由此,本地客户端将询问任何身份验证程序是否了解这些凭据句柄。如果是这样,用户就可以使用该验证器,并释放使用与该凭据句柄关联的公钥签名的身份验证方法。
这通常用于双因素身份验证,其中一些主要因素(如用户名和密码,或先前的身份验证会话)用于查找潜在的凭据句柄。
有些网站会要求用户名,然后返回凭据句柄,并使用不可发现的凭据作为主要因素,但这也有缺点:
-
以前关于身份验证程序工作方式的规范U2F不支持任何额外的用户验证。因此,这种身份验证只会提供一个占有因素——一个知道你可以拿走你的Yubiky并使用它进入这些服务的人。
-
由于您在没有任何初始身份验证的情况下在输入用户名/电子邮件时释放凭据句柄,因此您会泄露有关用户的信息——他们在该电子邮件中有一个帐户,他们正在使用WebAuthn,有多少身份验证器注册到他们的帐户,等等
相反,可以在不提供凭据句柄的情况下使用可发现凭据。这允许这些"无用户名"流——当触发时,依赖方只需询问"你有我可以使用的凭据吗",客户端则询问验证器他们存储了什么是合适的。
这种方法的缺点是可用性:
-
较旧的U2F硬件密钥不支持可发现凭据
-
支持可发现凭证的现代硬件密钥可能具有有限的闪存。
然而,将密钥集成到具有相对无限存储的平台中将很快改变可用性等式。
我不推荐@IAmKale使用随机值作为可发现凭据的方法。首先,用户很可能会看到name和displayName值,而垃圾字符串将不是一个好的用户体验。
其次,您可以放心使用相同的用户id注册凭据以覆盖现有凭据。但是,您无法删除以前在您的网站上注册的凭据。在每次注册时随机生成不同的值,可能会向用户提供凭据选择,即使只有其中一个凭据可以成功登录。即使客户端或平台有办法让用户删除虚假条目,这也不是一种很好的体验。
相反,我建议:
- 但是,您可以识别用户,关联并持久化webauthn用户id的新随机值
- 当您为该用户注册凭据时,请提供您可能拥有的任何澄清信息(例如他们的联系人电子邮件)以及该持久标识符
有两个独立的东西。
- 联合与Webauthn
Webauthn不是联邦凭据,所以你不能把它们的身份验证外包给电子邮件提供商或苹果、谷歌等。如果你想这样做,你需要这样做,但你已经在做了,我想你想要一些不同的东西?A有一种感觉,你想摆脱联邦身份验证,但你想继续外包身份验证,这就是:联邦身份验证(要么有蛋糕吃,要么反过来)。
通过联合,您的用户是电子邮件帐户或苹果、谷歌等帐户的所有者。无需注册,因为您的用户已在其他地方注册。你从其他人那里得到一个id和一个身份验证服务。。。
如果您想要webauthn,您需要首先对自己进行身份验证并使用create()注册用户。在服务器上存储内部uid->pid(在webauthn规范中被称为用户id和用户句柄的密钥id,但它实际上是一个密钥id)和pid->公钥。在客户端,通行证管理器(验证器)存储:xyz.com->{pid,私钥}并同步它。我将把passkey称为私钥。。。有些人把它用作钥匙对。
你的用户匿名身份是pid,或者更确切地说是一组pid,因为它似乎倾向于为每个帐户创建多个密钥,而不是解决一个密钥的同步。。。您可以使用作为公钥的验证密钥来验证标识或pid。
通过登录过程,调用get(),通行证管理器用私钥对随机动态服务器质询进行签名,然后用服务器上相应的公钥验证签名,从而验证pid身份。
这是一个非常好的数学,匿名和防钓鱼等,但验证身份的是你(!),而不是其他人!因此,首先你必须创建它,甚至更糟:从现在起,在苹果、谷歌、微软和后来的linux平台上,都是单独的(一组pid,一组公钥)。
我不确定它是否会埋葬webauthn密钥,因为从长远来看,它将是用户/网站所有者的维护地狱。这也是一个安全问题,因为许多用户在之前从未使用过通行证管理器,或者不知道自己实际上使用了通行证管理者的情况下,就会开始创建大量的通行证。。。Webauthn密钥需要一个通行证管理器,有些人不想使用或不知道他们使用了一个。。。它可能在一开始就起作用,但这意味着人们都能够处理通行证管理器。我不确定。。。
- 具有2个必需用户名的无用户名内部逻辑
我认为你是正确的,不使用用户名和电子邮件地址是一个选项,这很酷。可悲的是,webauthn在逻辑上是无用户名的(pid是真正的匿名身份,通行证管理器使用pid,2个用户名只是标签,以防每个域有多个帐户),但webauthn规范有一个逻辑错误,不仅需要一个用户名字段,还需要2个用户名字段。。。我个人认为这很糟糕,至少他们应该让这些字段成为可选字段。默认的想法应该是,一个真实的人每个域有一个帐户,然后整个用户体验会非常好和简单:;为xyz.com创建密钥";以及";使用xyz.com的密钥";(每个域每个通行证管理器有一个条目,没有任何用户名,简化了用户体验,减少了不必要的文本)。
如果有人创建了一个以上的帐户,这是可能的,这个人有责任管理通行证管理器条目标签并区分帐户(一个可选的注释字段而不是两个用户名字段就足够了…)。这不是常见的想法,但非常合乎逻辑:)Webauthn应该完全废除用户名字段,因为他们添加了pid(用户id、用户句柄-他们设法称之为两种不同的东西)。网站所有者没有责任对用户通行证管理器中的通行证管理标签进行微观管理。每个域拥有2个帐户的冲突实际上是一个错误,是由用户创建的,这是一种设计气味(就像网站使用电子邮件作为用户名,用户也想要一个燃烧器帐户,如果不需要真实身份的帐户停止收集个人可识别信息,他们一开始就不想要这个帐户)。
有关此主题的详细信息,请点击此处:现有密钥的用户名和显示名称更改
总而言之,webauthn不是联邦的,而是你必须通过";登录";使用get(),为此您需要第一步通过create()注册匿名身份。
用户名一团糟,我认为你有正确的感觉,它不应该在那里。