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

缓存CognitoUser丢失属性致Auth.completeNewPassword报错求助

解决AWS Cognito NEW_PASSWORD_REQUIRED挑战时用户对象丢失方法的问题

这个问题的核心原因很明确:Cognito返回的result是一个带有方法的CognitoUser实例对象,而localStorage、sessionStorage或Amplify Cache这类存储机制只能序列化和存储JSON格式的数据。当你把实例存入缓存时,对象里的方法(比如completeNewPasswordChallenge)会被丢弃,取出后变成了一个普通的JSON对象,自然会触发user.completeNewPasswordChallenge is not a function错误。

下面给你两种实用的解决方案:

方案一:在同一个组件内切换表单(推荐)

既然跳转页面会导致内存中的用户实例丢失,不如直接在登录组件里根据挑战状态切换显示登录表单和新密码表单,这样用户实例可以一直保存在组件的state中,完全避免序列化的问题。

修改你的Login.js代码如下:

import React, { Component } from 'react';
import { Auth } from 'aws-amplify';

class Login extends Component {
  state = {
    email: '',
    password: '',
    isLoading: false,
    showNewPasswordForm: false,
    cognitoUser: null,
    newPassword: ''
  };

  handleLogin = async () => {
    this.setState({ isLoading: true });
    try {
      const result = await Auth.signIn(this.state.email, this.state.password);
      if (result.challengeName === 'NEW_PASSWORD_REQUIRED') {
        // 切换到新密码表单,保存用户实例到state
        this.setState({
          showNewPasswordForm: true,
          cognitoUser: result,
          isLoading: false
        });
      } else {
        this.props.userHasAuthenticated(true);
        this.props.history.push("/");
      }
    } catch (e) {
      alert(e.message);
      this.setState({ isLoading: false });
    }
  };

  handleCompleteNewPassword = async () => {
    this.setState({ isLoading: true });
    try {
      await Auth.completeNewPassword(this.state.cognitoUser, this.state.newPassword);
      this.props.userHasAuthenticated(true);
      this.props.history.push("/");
    } catch (e) {
      alert(e.message);
      this.setState({ isLoading: false });
    }
  };

  render() {
    const { showNewPasswordForm, email, password, newPassword, isLoading } = this.state;

    if (showNewPasswordForm) {
      return (
        <div className="login-container">
          <h2>设置新密码</h2>
          <input
            type="password"
            value={newPassword}
            onChange={(e) => this.setState({ newPassword: e.target.value })}
            placeholder="请输入新密码"
            disabled={isLoading}
          />
          <button onClick={this.handleCompleteNewPassword} disabled={isLoading}>
            {isLoading ? '处理中...' : '确认设置'}
          </button>
        </div>
      );
    }

    return (
      <div className="login-container">
        <h2>登录</h2>
        <input
          type="email"
          value={email}
          onChange={(e) => this.setState({ email: e.target.value })}
          placeholder="邮箱"
          disabled={isLoading}
        />
        <input
          type="password"
          value={password}
          onChange={(e) => this.setState({ password: e.target.value })}
          placeholder="密码"
          disabled={isLoading}
        />
        <button onClick={this.handleLogin} disabled={isLoading}>
          {isLoading ? '登录中...' : '登录'}
        </button>
      </div>
    );
  }
}

export default Login;

这种方案不需要额外的存储操作,代码更简洁,用户体验也更流畅(没有页面跳转的感知)。

方案二:序列化必要属性,重新构造CognitoUser实例(适合必须跳转页面的场景)

如果你必须跳转到独立的新密码页面,那么需要存储CognitoUser实例中序列化后还能恢复的核心属性,然后在新页面重新构造一个合法的CognitoUser对象。

步骤1:修改Login.js的存储逻辑

只存储实例中的关键可序列化属性,而不是整个对象:

// Login.js中处理NEW_PASSWORD_REQUIRED的部分
if (result.challengeName === 'NEW_PASSWORD_REQUIRED') {
  // 提取可序列化的核心属性
  const serializedUser = {
    username: result.username,
    session: result.Session,
    challengeName: result.challengeName,
    challengeParameters: result.challengeParameters,
    // 需要你的用户池ID和客户端ID,建议从环境变量获取
    userPoolId: process.env.REACT_APP_USER_POOL_ID,
    clientId: process.env.REACT_APP_CLIENT_ID
  };
  Cache.setItem("CognitoUser", serializedUser);
  this.props.history.push("/login/newPassword");
}

步骤2:修改NewPassword.js的恢复逻辑

导入CognitoUser和CognitoUserPool类,重新构造实例:

import React, { Component } from 'react';
import { Auth } from 'aws-amplify';
import { CognitoUser, CognitoUserPool } from 'amazon-cognito-identity-js';

class NewPassword extends Component {
  state = {
    newPassword: '',
    isChanging: false
  };

  handleSetNewPassword = async () => {
    this.setState({ isChanging: true });
    try {
      const serializedUser = Cache.getItem("CognitoUser");
      if (!serializedUser) {
        alert("用户会话已过期,请重新登录");
        this.props.history.push("/login");
        return;
      }

      // 重新创建用户池实例
      const userPool = new CognitoUserPool({
        UserPoolId: serializedUser.userPoolId,
        ClientId: serializedUser.clientId
      });

      // 构造CognitoUser对象
      const user = new CognitoUser({
        Username: serializedUser.username,
        Pool: userPool
      });

      // 恢复会话和挑战信息
      user.Session = serializedUser.session;
      user.challengeName = serializedUser.challengeName;
      user.challengeParameters = serializedUser.challengeParameters;

      // 完成新密码设置
      await Auth.completeNewPassword(user, this.state.newPassword);
      this.props.history.push("/");
      Cache.removeItem("CognitoUser");
    } catch (e) {
      alert(e.message);
      this.setState({ isChanging: false });
    }
  };

  render() {
    const { newPassword, isChanging } = this.state;
    return (
      <div className="new-password-container">
        <h2>设置新密码</h2>
        <input
          type="password"
          value={newPassword}
          onChange={(e) => this.setState({ newPassword: e.target.value })}
          placeholder="请输入新密码"
          disabled={isChanging}
        />
        <button onClick={this.handleSetNewPassword} disabled={isChanging}>
          {isChanging ? '处理中...' : '确认设置'}
        </button>
      </div>
    );
  }
}

export default NewPassword;

需要注意的是,这种方法依赖于amazon-cognito-identity-js库的内部结构,如果库版本更新,可能需要调整属性名,但目前主流版本都是兼容的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:26:59