使用amazon-cognito-identity-js实现带二维码的TOTP MFA遇阻
问题描述
实现amazon-cognito-identity-js库的MFA二维码绑定流程时,遇到以下问题:
- 已通过
associateSoftwareToken获取密钥并生成二维码,也能从认证器获取TOTP码,但无法将TOTP传入verifySoftwareToken完成认证 - 提交TOTP时触发网络错误
- 尝试用
useState保存CognitoUser和AuthenticationDetails实例,无法维持认证上下文
需求目标
替换官方示例中的prompt弹窗,改用页面输入框让用户输入TOTP,点击提交后完成MFA绑定并跳转首页。
用户尝试的代码
初始登录处理逻辑
const [totpInput, setTotpInput] = useState(""); // 绑定二维码下方的输入框 // 初始登录事件处理函数 const onSubmit = (event) => { const authDetails = new AuthenticationDetails({ Username: email, Password: password }); const user = new CognitoUser({ Username: email, Pool: UserPool }); user.authenticateUser(authDetails, { // ... 省略成功、错误处理及其他回调函数 mfaSetup: (challengeName) => { user.associateSoftwareToken({ onFailure: (err) => { console.error(err); }, associateSecretCode: (secretCode) => { setQRCodeSecret(secretCode); setQRCode(true); // 渲染二维码组件 // 原示例此处用prompt获取TOTP并立即调用verifySoftwareToken,需修改逻辑 } }); } }); }
TOTP提交处理逻辑
// 二维码表单提交时执行 const onSubmitTotp = (totpInput) => { user = new CognitoUser({ Username: email, Pool: UserPool }) user.verifySoftwareToken(totpInput, 'cry5', { onSuccess: (result) => { console.log(result); }, onFailure: (err) => { console.log(err); } }); }
无效的状态保存尝试
// 全局定义状态 const [user, setUser] = useState(); const [authDetails, setAuthDetails] = useState(); // 在onSubmit开头设置状态 setUser(new CognitoUser({ Username: email, Pool: UserPool })); setAuthDetails(new AuthenticationDetails({ Username: email, Password: password }));
解决方案
核心问题是:必须使用authenticateUser流程中创建的同一个CognitoUser实例调用verifySoftwareToken,不能重新创建新实例。React的useState无法持久化带内部状态的CognitoUser实例,改用useRef来保存实例上下文。
步骤1:用useRef持久化CognitoUser实例
import { useRef, useState } from 'react'; // 用useRef保存实例,避免组件重渲染时丢失认证上下文 const cognitoUserRef = useRef(null); const [qrCodeSecret, setQRCodeSecret] = useState(''); const [showQRCode, setShowQRCode] = useState(false); const [totpInput, setTotpInput] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState('');
步骤2:修改初始登录流程,保存实例到ref
const onSubmit = (event) => { event.preventDefault(); const authDetails = new AuthenticationDetails({ Username: email, Password: password }); const user = new CognitoUser({ Username: email, Pool: UserPool }); cognitoUserRef.current = user; // 将实例存入ref user.authenticateUser(authDetails, { onSuccess: (result) => { // 正常登录成功,跳转首页 window.location.href = '/'; }, onFailure: (err) => { console.error('登录失败:', err); }, mfaSetup: (challengeName) => { user.associateSoftwareToken({ onFailure: (err) => { console.error('MFA绑定初始化失败:', err); }, associateSecretCode: (secretCode) => { setQRCodeSecret(secretCode); setShowQRCode(true); // 显示二维码和输入框 } }); } }); }
步骤3:修改TOTP提交函数,使用ref中的实例
const onSubmitTotp = (event) => { event.preventDefault(); if (!cognitoUserRef.current) { console.error('用户实例不存在,请先完成初始登录'); return; } // friendlyDeviceName可自定义,不影响认证逻辑 cognitoUserRef.current.verifySoftwareToken(totpInput, 'MyDevice', { onSuccess: (result) => { console.log('MFA绑定成功:', result); // 绑定完成后跳转首页 window.location.href = '/'; }, onFailure: (err) => { console.error('TOTP验证失败:', err); } }); }
步骤4:页面渲染示例
return ( <div> {!showQRCode ? ( // 初始登录表单 <form onSubmit={onSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> <button type="submit">登录</button> </form> ) : ( // MFA绑定界面 <div> {/* 生成二维码:用otpauth格式拼接密钥,确保认证器可识别 */} <img src={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=otpauth://totp/你的应用名称:${email}?secret=${qrCodeSecret}&issuer=你的应用名称`} alt="MFA二维码" /> <form onSubmit={onSubmitTotp}> <input type="text" value={totpInput} onChange={(e) => setTotpInput(e.target.value)} placeholder="输入6位验证码" maxLength={6} required /> <button type="submit">完成绑定</button> </form> </div> )} </div> );
关键注意事项
useRef保存的实例在组件生命周期内不会被重置,能保留CognitoUser内部的认证会话,这是调用verifySoftwareToken成功的核心- 绝对不能重新创建CognitoUser实例,否则会丢失之前的认证流程状态,导致网络错误
- 二维码的
otpauth格式必须正确,否则认证器无法识别密钥
内容的提问来源于stack exchange,提问作者codiana
相关产品推荐
相关产品推荐

