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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 09:45:42