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

使用SimpleWebAuthn+Node.js+React.js实现Passkey登录时遇No data错误

问题描述

基于React.js前端、Node.js后端和MongoDB数据库开发项目,尝试通过Passkey实现登录功能,已按照SimpleWebAuthn官方文档编写代码,但登录时后端持续抛出CBOR解码错误。

后端注册相关代码

const registerWebAuthentication = async (req, res) => {
  console.log("------------------------>>>>",req.body);
  try {
    const user = await User.findOne({ username: req.body.username });
    console.log("from registerWebauthentication: " + req.body);
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }
    const challengePayload = await SimpleWebAuthnServer.generateRegistrationOptions({
      rpID: 'localhost',
      rpName: 'nobil localhost',
      userName: user.username,
    })
    await User.findOneAndUpdate(
      { username: req.body.username },
      { challenge: challengePayload.challenge }
    );
    return res.json({ options: challengePayload })
  } catch (e) {
    console.log(e);
  }
}

const verifyRegistration = async(req, res) => {
  console.log("inside verify registrations ===========>>", req.body);
  try {
    const user = await User.findOne({ username: req.body.username });
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }

    const expectedChallenge = user.challenge;

    const verification = await SimpleWebAuthnServer.verifyRegistrationResponse({
      response: req.body.cred,
      expectedChallenge: expectedChallenge,
      expectedOrigin: 'http://localhost:3000', 
      expectedRPID: 'localhost',
    });

    if (!verification.verified) return res.json({ error: 'could not verify' });
    await User.findOneAndUpdate(
      { username: req.body.username },
      { passkey: verification.registrationInfo }
    );

    return res.json({ verified: true });
  } catch (e) {
    console.log(e);
  }
}

后端登录相关代码

const generateLoginOptions = async (req, res) => {
  console.log("generate login options called ---------------------->", req.body);
  try {
    const userTofind = await user.findOne({ username: req.body.username });
    if (!userTofind) {
      return res.status(404).json({ message: 'User not found' });
    } else {
      console.log("user found for generating login options ------------------->");
    }

    const loginOptions = await SimpleWebAuthnServer.generateAuthenticationOptions({
      rpID: 'localhost',
    });

    console.log("login options from backend: ----------------", loginOptions);
    await user.findOneAndUpdate(
      { username: req.body.username },
      { challenge: loginOptions.challenge }
    );

    res.json({ options: loginOptions });
  } catch (e) {
    console.error(e);
    res.status(500).json({ message: 'Internal Server Error' });
  }
};

const verifyLogin = async (req, res) => {
  console.log("verify login called ----------------> ", req.body);
  try {
    const userToLogin = await user.findOne({ username: req.body.username });
    if (!userToLogin) {
      return res.status(404).json({ message: 'User not found' });
    }

    const expectedChallenge = userToLogin.challenge;
    console.log("this is your expected challenge: ---------------------" , expectedChallenge);
    console.log("this is for cred testing -----------> ",req.body.cred)
    console.log("the passkey publicKey: =------------->", userToLogin.passkey);
    const verification = await SimpleWebAuthnServer.verifyAuthenticationResponse({
      response: req.body.cred,
      expectedChallenge: expectedChallenge,
      expectedOrigin: 'http://localhost:3000',
      expectedRPID: 'localhost',
      authenticator: {
        credentialID: userToLogin.passkey.credentialID,
        credentialPublicKey: userToLogin.passkey.credentialPublicKey,
        counter: userToLogin.passkey.counter,
      },
    });

    if (!verification.verified) return res.json({ error: 'could not verify' });
    const token = jwt.sign({ username: userToLogin.username, role: userToLogin.role }, "everidoor", {
      expiresIn: "5h",
    });
    if (result.role == "advertiser" || result.role == "display-provider") {
      console.log("------------- token is:", result.role);
      res.status(200).send({
        Message: "Login Successful",
        role: result.role,
        DpUsername: result.DpUsername,
        cid: result.customerId,
        pin: result.pin,
        token: token,
        verified: true,
      });
    } else {
      res.status(202).send({
        Message: "Login Successful",
        role: result.role,
        DpUsername: result.DpUsername,
        loginCount: result.loginCount,
        token: token,
        verified: true,
      });
    }
  } catch (e) {
    console.error(e);
    res.status(500).json({ message: 'Internal Server Error' });
  }
};

前端登录处理代码

const handlePasskeyLogin = async () => {
      try {
        const res = await Axios.post('/generate-login-options', { username: passkeyUsername });
        console.log("following are the options: ", res);
        const options = res.data.options;
        const loginRes = await startAuthentication(options);
        console.log(loginRes);
        const verifyRes = await Axios.post('/verify-passkey-login', { username: passkeyUsername, cred: loginRes });
        console.log("response from server on verification of passkey login: ", verifyRes);

        // if (verifyRes.data.verified) {
        //   setAdvertiser(verifyRes.data.user);
        //   setSigned(true);
        //   navigate("/home");
        // } else {
        //   notify("Passkey login failed");
        // }

        if(verifyRes.data.role === "display-provider"){
          localStorage.setItem("Dp_token", verifyRes?.data?.token);
        }
        else{
          localStorage.setItem("token", verifyRes?.data?.token);
        }
        
        console.log(verifyRes?.data?.token);

        sessionStorage.setItem("role", verifyRes.data);
        localStorage.setItem("Admin", verifyRes.data.DpUsername);
         console.error("An error occurred:", verifyRes?.status);
        if (verifyRes?.status === 200) {
          setAdvertiser(verifyRes.data)
          sessionStorage.setItem("role", verifyRes.data.role);
          setSigned(true);
          navigate("/home");

        } 
       
         else if (verifyRes?.status === 202) {
          setSigned(true);
          
          if (verifyRes.data.role === "Manager") {
            sessionStorage.setItem("role", verifyRes.data.role);
            navigate("/Manager");
          } else if (verifyRes.data.role === "Publisher") {
            sessionStorage.setItem("role", verifyRes.data.role);
            navigate("/Publisher");
          } else if (verifyRes.data.role === "Viewer") {
            sessionStorage.setItem("role", verifyRes.data.role);
            navigate("/Viewer");
          }
        }
      } catch (error) {
        console.error(error);
        notify("Error during passkey login");
      }
    };

报错信息

Error: No data
    at Object.decodePartialCBOR (C:\Users\nobil\OneDrive\Desktop\MOLOG\EveridoorBackend2.0\node_modules\@levischuck\tiny-cbor\script\cbor\cbor.js:355:15)
    at Object.decodeFirst (C:\Users\nobil\OneDrive\Desktop\MOLOG\EveridoorBackend2.0\node_modules\@simplewebauthn\server\script\helpers\iso\isoCBOR.js:25:40)
    at decodeCredentialPublicKey (C:\Users\nobil\OneDrive\Desktop\MOLOG\EveridoorBackend2.0\node_modules\@simplewebauthn\server\script\helpers\decodeCredentialPublicKey.js:6:84)
    at verifySignature (C:\Users\nobil\OneDrive\Desktop\MOLOG\EveridoorBackend2.0\node_modules\@simplewebauthn\server\script\helpers\verifySignature.js:20:86)
    at Object.verifyAuthenticationResponse (C:\Users\nobil\OneDrive\Desktop\MOLOG\EveridoorBackend2.0\node_modules\@simplewebauthn\server\script\authentication\verifyAuthenticationResponse.js:157:66)
    at async verifyLogin (C:\Users\nobil\OneDrive\Desktop\MOLOG\EveridoorBackend2.0\controllers\user.controller.js:147:26)
POST /verify-passkey-login 500 52.308 ms - 35

解决方案

1. 修复Passkey存储格式问题

错误根源是MongoDB无法直接存储Uint8Array类型数据,注册时verification.registrationInfo中的credentialID和credentialPublicKey是Uint8Array,存入数据库会被自动转为普通数组,导致登录验证时CBOR解码失败。

修改后端verifyRegistration中的存储逻辑,将Uint8Array转为Base64字符串存储:

// 原代码
await User.findOneAndUpdate(
  { username: req.body.username },
  { passkey: verification.registrationInfo }
);

// 修改后
await User.findOneAndUpdate(
  { username: req.body.username },
  { 
    passkey: {
      ...verification.registrationInfo,
      credentialID: Buffer.from(verification.registrationInfo.credentialID).toString('base64'),
      credentialPublicKey: Buffer.from(verification.registrationInfo.credentialPublicKey).toString('base64')
    }
  }
);

2. 修复登录验证时的格式转换

在verifyLogin中,将从数据库取出的Base64字符串转回Uint8Array:

// 原代码
const authenticator = {
  credentialID: userToLogin.passkey.credentialID,
  credentialPublicKey: userToLogin.passkey.credentialPublicKey,
  counter: userToLogin.passkey.counter,
};

// 修改后
const authenticator = {
  credentialID: Buffer.from(userToLogin.passkey.credentialID, 'base64'),
  credentialPublicKey: Buffer.from(userToLogin.passkey.credentialPublicKey, 'base64'),
  counter: userToLogin.passkey.counter,
};

3. 修复其他代码错误

  • 登录代码中result变量未定义,全部替换为userToLogin:
    // 原代码
    if (result.role == "advertiser" || result.role == "display-provider") { ... }
    
    // 修改后
    if (userToLogin.role == "advertiser" || userToLogin.role == "display-provider") { ... }
    
  • generateLoginOptions中的user.findOne改为User.findOne(与注册代码中的模型引用保持大小写一致)。

内容的提问来源于stack exchange,提问作者Nobil Gautam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:44:56