浏览器支持名为
WebAuthn
的新安全标准,以促进强认证。我们开源了一个库来促进它的实现。
密码很糟糕:当密码足够长、足够安全的时候,就很难记住了。我们需要为每个新服务生成一个新密码,并且禁止大约5.5亿个密码,因为它们是数据泄露的一部分。
有哪些替代身份验证方法?我们需要依靠拥有因素(用户拥有的东西,比如安全令牌)或内在因素(用户所拥有的东西,比如指纹),而不是知识因素(用户知道的东西)。或者,理想情况下,结合使用这些方法(即多因素身份验证,或MFA)。
遗憾的是,并非每个用户都拥有带指纹传感器的设备,而且软件令牌(如Google身份验证器应用程序)要求用户将一次性密码(OTP)从令牌复制到他们想要登录的服务。换句话说:强认证对于使用的终端来说是很麻烦的。
一个很好解决的方案是
连接令牌
,即插入计算机的设备,如USB密钥。这是一个占有因素,你可以在任何电脑上使用它。在连接到计算机时,你无需手动复制OTP。它使用公钥加密技术,只需单击即可验证质询。
这是一个连接的令牌,称为YubiKey,由Yubico公司销售:
但是,需要访问令牌的服务必须知道其协议。直到最近,只有胖客户端才能与连接的令牌进行通信。这排除了在浏览器中运行的服务。
WebAuthn / Fido2:一个W3C标准,W3C标准将强认证带入浏览器
输入FIDO2,这是浏览器中强认证的标准。引用维基百科:
FIDO2的核心是由W3C Web 身份验证 (WebAuthn)标准和FIDO 客户端到身份验证器协议(CTAP)组成。
WebAuthn标准是一组JavaScript API,用于在浏览器中与连接的令牌进行通信。在撰写本文时,大多数桌面浏览器都支持它,覆盖全球67%的用户:
它允许做的是使用连接的令牌注册并登录到Web应用程序。无需密码要:只需插上密钥,按下按钮,就可以了。
如果你曾经使用过YubiKey或类似的东西,你会同意这种体验比输入密码要好一个数量级。但是,为什么我们不在我们的web应用程序中使用WebAuthn呢?
客户端-服务器协议很难
还记得FIDO2项目中的客户端到认证器协议规范(CTAP)吗?它是一种协议,意味着它的实现取决于开发人员,问题就在这里。
已经有一些实现,你可以在GitHub和WebAuthn.io网站上找到。
WebAuthn.io官网:
https://webauthn.io/
这些实现通常是由与FIDO2联盟成员开发的概念验证,这意味着这些库仅在少数情况下工作,并且它们的文档要么不存在,要么难以理解。
例如,下面是FIDO联盟自己编写的JavaScript webauthn-demo自述文件:
webauthn-demo地址:
https://github.com/fido-alliance/webauthn-demo
它是JavaScript中唯一的一个。
介绍@webauthn包
现在,我们介绍
@webauthn/client
和
@webauthn/server
这两个NPM包,它们将帮助JavaScript开发人员在实践中实现FIDO2。我们还在这些包中包含了一个示例客户端和服务器。
下面是如何在客户端实现注册按钮:
import { solveRegistrationChallenge, solveLoginChallenge } from '@webauthn/client';
const loginButton = document.getElementById('login');
const registerButton = document.getElementById('register');
const messageDiv = document.getElementById('message');
const displayMessage = message => {
messageDiv.innerHTML = message;
}
registerButton.onclick = async () => {
const challenge = await fetch('https://localhost:8000/request-register', {
method: 'POST',
headers: {
'content-type': 'Application/Json'
},
body: JSON.stringify({ id: 'uuid', email: 'test@test' })
})
.then(response => response.json());
const credentials = await solveRegistrationChallenge(challenge);
const { loggedIn } = await fetch(
'https://localhost:8000/register',
{
method: 'POST',
headers: {
'content-type': 'Application/Json'
},
body: JSON.stringify(credentials)
}
).then(response => response.json());
if (loggedIn) {
displayMessage('registration successful');
return;
}
displayMessage('registration failed');
};
loginButton.onclick = async () => {
const challenge = await fetch('https://localhost:8000/login', {
method: 'POST',
headers: {
'content-type': 'Application/Json'
},
body: JSON.stringify({ email: 'test@test' })
})
.then(response => response.json());
const credentials = await solveLoginChallenge(challenge);
const { loggedIn } = await fetch(
'https://localhost:8000/login-challenge',
{
method: 'POST',
headers: {
'content-type': 'Application/Json'
},
body: JSON.stringify(credentials)
}
).then(response => response.json());
if (loggedIn) {
displayMessage('You are logged in');
return;
}
displayMessage('Invalid credential');
};
@webauthn/client公开的两个方法只是Navigator.credentials API的一个瘦包装器。实际上,浏览器管理加密挑战,这样开发人员就不会尝试自己完成(错误的方式)。
下面是服务器部分,使用Node.js和Express.js:
const {
generateRegistrationChallenge,
parseRegisterRequest,
} = require('@webauthn/server');
const