You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用SimpleWebAuthn、React和Node.js实现PassKey认证时遇解码错误

PassKey认证失败:verifyAuthenticationResponse调用报错排查

尝试使用PassKey实现用户认证时,调用verifyAuthenticationResponse方法无法完成登录,服务器抛出意外错误。曾怀疑MongoDB存储的数据格式问题,转换为普通JS对象后问题仍未解决,MongoDB中passkey字段类型为Mixed。

相关代码

exports.loginVerification = async (req, res) => {
  try {
    const { username, credential } = req.body;
    if (!username || !credential) {
      return res.status(400).json({
        status: "fail",
        message: "Username and credential are required",
      });
    }

    const user = await User.findOne({ username }).select("-password");
    if (!user) {
      return res.status(404).json({
        status: "fail",
        message: "User not found",
      });
    }

    const challenge = await Challenge.findOne({ userId: user._id });
    if (!challenge) {
      return res.status(404).json({
        status: "fail",
        message: "Challenge not found",
      });
    }

    const userObject = user.toObject();
    const credentialId = userObject.passkey.credentialID;
    const credentialPublicKey = userObject.passkey.credentialPublicKey;
    const counter = userObject.passkey.counter;

    console.log({
      passkey: userObject.passkey,
    });

    const result = await verifyAuthenticationResponse({
      expectedOrigin: process.env.ORIGIN,
      expectedRPID: process.env.RP_ID,
      response: credential,
      expectedChallenge: challenge.challenge,
      authenticator: userObject.passkey,
    });

    console.log({ result });

    if (!result.verified) {
      return res.status(401).json({
        status: "fail",
        message: "Verification failed",
      });
    }

    res.status(200).json({
      status: "success",
      verified: result.verified,
      message: "Verification successful",
      user,
    });
  } catch (error) {
    console.log(error);
    res.status(400).json({
      status: "fail",
      message: error.message,
    });
  }
};

错误栈信息

Error: No data
    at Object.decodePartialCBOR (D:\fullstack\passkey-authentication\server\node_modules\@levischuck\tiny-cbor\script\cbor\cbor.js:355:15)
    at Object.decodeFirst (D:\fullstack\passkey-authentication\server\node_modules\@simplewebauthn\server\script\helpers\iso\isoCBOR.js:25:40)
    at decodeCredentialPublicKey (D:\fullstack\passkey-authentication\server\node_modules\@simplewebauthn\server\script\helpers\decodeCredentialPublicKey.js:6:84)
    at verifySignature (D:\fullstack\passkey-authentication\server\node_modules\@simplewebauthn\server\script\helpers\verifySignature.js:20:86)
    at verifyAuthenticationResponse (D:\fullstack\passkey-authentication\server\node_modules\@simplewebauthn\server\script\authentication\verifyAuthenticationResponse.js:157:66)
    at async exports.loginVerification (D:\fullstack\passkey-authentication\server\controllers\challenge.controller.js:182:20)

响应输出

响应输出

问题原因及解决办法

原因分析

错误栈指向CBOR解码失败(No data),核心问题是存储在MongoDB中的credentialPublicKey格式不符合@simplewebauthn/server的要求。PassKey的公钥是CBOR编码的二进制数据,需要以Buffer类型传入验证函数,但MongoDB的Mixed类型可能将原始Buffer转成了Base64字符串、MongoDB Binary对象或其他格式,导致解码时无法识别有效数据。

解决步骤

  1. 检查存储格式:查看MongoDB中passkey.credentialPublicKey的实际存储形式——如果是Base64字符串、十六进制字符串或MongoDB Binary对象,需要转换为Node.js Buffer类型。
  2. 修改代码转换格式:在获取用户passkey数据后,手动将credentialID和credentialPublicKey转换为Buffer,再构造符合要求的authenticator对象:
// 替换原passkey字段处理逻辑
const userObject = user.toObject();

// 根据实际存储格式转换,这里假设是Base64字符串
const credentialId = Buffer.from(userObject.passkey.credentialID, 'base64');
// 公钥必须是Buffer类型才能被CBOR解码
const credentialPublicKey = Buffer.from(userObject.passkey.credentialPublicKey, 'base64');
const counter = userObject.passkey.counter;
const transports = userObject.passkey.transports || [];

// 构造符合@simplewebauthn/server要求的authenticator对象
const authenticator = {
  credentialID: credentialId,
  credentialPublicKey: credentialPublicKey,
  counter: counter,
  transports: transports,
};

// 调用验证函数时传入转换后的authenticator
const result = await verifyAuthenticationResponse({
  expectedOrigin: process.env.ORIGIN,
  expectedRPID: process.env.RP_ID,
  response: credential,
  expectedChallenge: challenge.challenge,
  authenticator: authenticator,
});
  1. 验证存储逻辑:确保用户注册时,credentialPublicKey是以原始Buffer或可正确反序列化为Buffer的格式存入MongoDB(避免自动转换为字符串)。

内容的提问来源于stack exchange,提问作者Varshil Shah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 19:07:11