使用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
相关产品推荐
相关产品推荐

