@flun/webauthn-server 是一个专为 Node.js 环境设计的 WebAuthn(含 Passkeys)服务端工具库,采用 TypeScript 编写,提供对证书、密钥及 CBOR 编码的完整处理能力。它简化了 FIDO2/Passkey 身份验证后端的构建过程,支持当前所有主流的 WebAuthn 认证格式。
模块规范:本包使用 ESM 模块编写。要求 Node.js ≥ 22.12.0,若使用低版本 Node 则需通过
import语法导入(不建议在低于 22.12.0 的环境中使用require())。
npm install @flun/webauthn-server注意:本库依赖 Node.js 内置的
crypto、fetch等模块,请确保运行环境为 Node.js ≥ 22.12.0。
-
✅ WebAuthn 注册与认证 提供
generateRegistrationOptions/verifyRegistrationResponse和generateAuthenticationOptions/verifyAuthenticationResponse两对核心方法,完整实现 WebAuthn 后端逻辑。 -
✅ 全格式证明支持 支持
packed、fido-u2f、android-safetynet、android-key、tpm、apple、none等所有标准认证格式。 -
✅ 元数据服务 (MDS) 内置
MetadataService可下载并解析 FIDO 联盟的元数据 BLOB,验证认证器真伪与状态。 -
✅ 证书链验证 提供证书路径验证、吊销检查、X.509 解析等工具。
-
✅ 丰富的辅助工具 通过
@flun/webauthn-server/helpers子路径导出大量底层工具:Base64URL 编解码、COSE ↔ PKCS 转换、authData 解析、签名验证等。
以下是一个基于 Express 的完整后端示例,展示了用户注册与登录的 WebAuthn 流程。
import express from 'express';
import {
generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse
} from '@flun/webauthn-server';
const app = express();
app.use(express.json());
// 模拟用户数据库
const users = {
'user@example.com': {
id: Buffer.from('user-id-buffer'),
credentials: []
}
};
// 临时挑战存储(生产环境建议使用 session 或 redis)
const challengeStore = new Map();
// 1. 注册 - 生成选项
app.post('/api/register/begin', async (req, res) => {
const { username } = req.body;
const user = users[username];
if (!user) return res.status(404).json({ error: 'User not found' });
const options = await generateRegistrationOptions({
rpName: 'My App',
rpID: 'localhost',
userName: username,
userID: user.id,
attestationType: 'none',
});
challengeStore.set(username + '_reg', options.challenge);
res.json(options);
});
// 2. 注册 - 验证响应
app.post('/api/register/complete', async (req, res) => {
const { username, response } = req.body;
const expectedChallenge = challengeStore.get(username + '_reg');
const verification = await verifyRegistrationResponse({
response,
expectedChallenge,
expectedOrigin: 'http://localhost:3000',
expectedRPID: 'localhost',
requireUserVerification: true,
});
if (verification.verified) {
const { credential } = verification.registrationInfo;
users[username].credentials.push({
id: credential.id,
publicKey: credential.publicKey,
counter: credential.counter,
transports: credential.transports
});
challengeStore.delete(username + '_reg');
res.json({ verified: true });
} else {
res.status(400).json({ verified: false });
}
});
// 3. 认证 - 生成选项
app.post('/api/login/begin', async (req, res) => {
const { username } = req.body;
const user = users[username];
if (!user) return res.status(404).json({ error: 'User not found' });
const options = await generateAuthenticationOptions({
rpID: 'localhost',
allowCredentials: user.credentials.map(cred => ({
id: cred.id,
type: 'public-key',
transports: cred.transports,
})),
});
challengeStore.set(username + '_auth', options.challenge);
res.json(options);
});
// 4. 认证 - 验证响应
app.post('/api/login/complete', async (req, res) => {
const { username, response } = req.body;
const expectedChallenge = challengeStore.get(username + '_auth');
const user = users[username];
const credential = user.credentials.find(c => c.id === response.id);
const verification = await verifyAuthenticationResponse({
response,
expectedChallenge,
expectedOrigin: 'http://localhost:3000',
expectedRPID: 'localhost',
credential,
});
if (verification.verified) {
credential.counter = verification.authenticationInfo.newCounter;
challengeStore.delete(username + '_auth');
res.json({ verified: true });
} else {
res.status(400).json({ verified: false });
}
});
app.listen(3001, () => console.log('Backend running on port 3001'));前端需使用配套的浏览器库 @flun/webauthn-browser,可通过 CDN 或 npm 引入。
<script src="https://unpkg.com/@flun/webauthn-browser/dist/index.js"></script>调用示例:
// 注册
const regOptions = await fetch('/api/register/begin', ...).then(r => r.json());
const regResponse = await flunWebAuthnBrowser.startRegistration({ optionsJSON: regOptions });
await fetch('/api/register/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, response: regResponse })
});
// 登录
const authOptions = await fetch('/api/login/begin', ...).then(r => r.json());
const authResponse = await flunWebAuthnBrowser.startAuthentication({ optionsJSON: authOptions });
await fetch('/api/login/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, response: authResponse })
});| 方法 | 描述 |
|---|---|
generateRegistrationOptions(options) |
生成用于 navigator.credentials.create() 的选项参数 |
verifyRegistrationResponse(options) |
验证客户端返回的注册响应,返回验证结果及凭证信息 |
| 方法 | 描述 |
|---|---|
generateAuthenticationOptions(options) |
生成用于 navigator.credentials.get() 的选项参数 |
verifyAuthenticationResponse(options) |
验证客户端返回的认证响应,返回验证结果及更新的计数器 |
| 导出项 | 描述 |
|---|---|
MetadataService |
协调 FIDO 元数据交互的基础服务实例(BaseMetadataService 的实例) |
SettingsService |
管理各类 attestation 声明格式根证书的服务实例 |
supportedCOSEAlgorithmIdentifiers |
支持的加密算法标识符数组 |
AlgSign |
支持的签名算法数组 |
可通过 @flun/webauthn-server/helpers 导入所有底层工具。
| 函数 | 描述 |
|---|---|
fromBuffer(buffer) |
将 ArrayBuffer 编码为 Base64URL 字符串 |
toBuffer(base64url) |
将 Base64URL 字符串解码为 ArrayBuffer |
utf8Tob64url(str) |
UTF-8 字符串 → Base64URL |
b64urlToUtf8(str) |
Base64URL → UTF-8 字符串 |
toBase64(base64url) |
Base64URL → 标准 Base64 |
isBase64(str) |
检查是否为 Base64 编码 |
isBase64URL(str) |
检查是否为 Base64URL 编码 |
trimPadding(str) |
移除 Base64URL 填充字符 |
fromHex(hex) |
十六进制字符串 → Uint8Array |
toHex(buf) |
Uint8Array → 十六进制字符串 |
utf8Tobytes(str) |
UTF-8 → Uint8Array |
bytesToUtf8(buf) |
Uint8Array → UTF-8 |
asciiToBytes(str) |
ASCII → Uint8Array |
areEqual(a, b) |
比较两个 Uint8Array 是否相等 |
concat(...arrays) |
拼接多个 Uint8Array |
toDataView(buf) |
转换为 DataView 对象 |
| 类型/函数 | 描述 |
|---|---|
COSEKEYS, COSEKTY, COSECRV, COSEALG |
COSE 标准键值枚举 |
COSEPublicKey, COSEPublicKeyOKP, COSEPublicKeyEC2, COSEPublicKeyRSA |
COSE 公钥类型 |
isCOSEPublicKeyOKP(), isCOSEPublicKeyEC2(), isCOSEPublicKeyRSA() |
类型守卫 |
isCOSEKty(), isCOSECrv(), isCOSEAlg() |
COSE 参数值检查 |
decodeCredentialPublicKey(buf) |
解码 CBOR 编码的凭证公钥为 Map 对象 |
convertCOSEtoPKCS(coseKey) |
将 COSE 公钥转换为 PKCS 格式 |
convertX509PublicKeyToCOSE(certDer) |
从 X.509 证书提取公钥并转换为 COSE 结构 |
| 函数/类 | 描述 |
|---|---|
convertCertBufferToPEM(buf) |
将证书缓冲区转换为 PEM 字符串 |
convertPEMToBytes(pem) |
将 PEM 证书转换为字节数组 |
getCertificateInfo(pem) |
提取 PEM 证书的颁发者、主题、有效期等信息 |
isCertRevoked(certPem) |
检查证书是否被其 CRL 吊销 |
validateCertificatePath(certs) |
验证证书链的有效性 |
validateExtFIDOGenCEAAGUID(cert, aaguid) |
校验证书中的 FIDO Gen CE AAGUID 扩展 |
InvalidSubjectAndIssuer |
证书链验证失败时抛出的错误类 |
| 函数/类型 | 描述 |
|---|---|
decodeAttestationObject(buf) |
解析 attestationObject 缓冲区 |
decodeClientDataJSON(base64url) |
解析 clientDataJSON |
parseAuthenticatorData(authData) |
解析 authData,返回可读结构 |
parseBackupFlags(flags) |
解析备份状态标志 (BE/BS) |
decodeAuthenticatorExtensions(buf) |
解码扩展数据 |
InvalidBackupFlags |
备份标志无效时抛出的错误类 |
| 函数 | 描述 |
|---|---|
verifySignature({ signature, data, publicKey }) |
通用签名验证入口 |
verifyEC2(), verifyOKP(), verifyRSA() |
特定算法签名验证 |
toHash(data, algorithm?) |
计算哈希摘要(默认 SHA-256) |
mapX509SignatureAlgToCOSEAlg(oid) |
X.509 签名算法 OID → COSE 算法 ID |
| 函数/类 | 描述 |
|---|---|
generateChallenge() |
生成随机挑战值 |
generateUserID() |
生成随机用户 ID |
matchExpectedRPID(rpIdHash, expectedRPIDs) |
匹配 RP ID 哈希值 |
UnexpectedRPIDHash |
RP ID 不匹配时抛出的错误类 |
fetch(url, options?) |
跨运行时的 fetch 封装 |
getLogger(name?) |
获取基于 debug 的日志记录器 |
convertAAGUIDToString(aaguid) |
将 AAGUID 缓冲区转换为 UUID 字符串 |
digest(algorithm, data) |
生成数据摘要 |
getRandomValues(arr) |
填充随机字节 |
verify(algorithm, key, signature, data) |
底层验签函数 |
本库完全兼容 W3C WebAuthn 规范 中定义的所有证明格式:
- Packed
- FIDO U2F
- Android SafetyNet
- Android Key
- TPM
- Apple
- None
每种格式的验证逻辑均已内置,无需额外配置。
库中大部分验证方法会在失败时抛出明确的错误类,例如:
UnexpectedRPIDHash:RP ID 哈希不匹配InvalidSubjectAndIssuer:证书链无效InvalidBackupFlags:备份标志组合非法
建议使用 try...catch 捕获这些错误并作出相应处理。
ISC © flun