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

AWS Cognito无密码自定义认证流返回‘用户名或密码错误’问题排查

AWS Cognito自定义无密码认证流问题排查:OTP验证成功仍返回NotAuthorizedException

场景描述

基于AWS Cognito实现手机号+WhatsApp OTP的无密码自定义认证流,已配置三个Lambda触发器:

  • DefineAuthChallenge:设置下一步挑战为CUSTOM_CHALLENGE
  • CreateAuthChallenge:生成OTP并通过WhatsApp发送
  • VerifyAuthChallengeResponse:验证用户提交的OTP

问题:执行到VerifyAuthChallengeResponse步骤时,即使OTP验证成功(Lambda返回event.response.answerCorrect=true),Cognito仍抛出错误:NotAuthorizedException: Incorrect username or password

已检查项

  • 手机号(用户名)为E.164格式
  • Cognito用户状态为CONFIRMED,且具备phone_number/phone_number_verified属性
  • Lambda响应属性无拼写错误

需要解答的问题

  1. 为何验证成功仍返回该错误?
  2. Lambda响应需设置哪些特定字段以确认认证成功?
  3. 是否因测试用户为自定义流实现前创建导致问题?

附相关代码

VerifyAuthChallengeResponse Lambda代码

exports.handler = async (event) => {
    const userAnswer = event.request.challengeAnswer;
    const correctAnswer = event.request.privateChallengeParameters.otp;

    if (userAnswer === correctAnswer) {
        event.response.answerCorrect = true;
    } else {
        event.response.answerCorrect = false;
    }

    return event;
};

NestJS认证代码

async initiateAuth(phoneNumber: string): Promise<any> {
    const command = new InitiateAuthCommand({
      ClientId: this.clientId,
      AuthFlow: "CUSTOM_AUTH",
      AuthParameters: {
        USERNAME: phoneNumber,
        password: "test@1234"
      },
    });

    try {
      const response = await this.client.send(command);
      return response;
    } catch (error: any) {
      this.logger.debug(
        `Error authenticating user, error: ${JSON.stringify(error)}`,
      );
      throw error;
    }
  }

  async respondToAuthChallenge(
    username: string,
    phoneNumber: string,
    otp: string,
    session: string,
  ): Promise<any> {
    const command = new RespondToAuthChallengeCommand({
      ClientId: this.clientId,
      ChallengeName: "CUSTOM_CHALLENGE",
      Session: session,
      ChallengeResponses: {
        USERNAME: username,
        ANSWER: otp,
      },
    });

    try {
      const response = await this.client.send(command);
      return response;
    } catch (error: any) {
      this.logger.debug(
        `Error responding to challenge: ${JSON.stringify(error)}`,
      );
      throw error;
    }
  }

排查思路与解决方案

1. 验证成功仍返回错误的原因

  • DefineAuthChallenge逻辑缺失:自定义认证流中,该触发器不仅要初始化挑战,还需在OTP验证成功后明确结束认证流。如果未判断event.request.session中的成功验证记录,Cognito会继续要求挑战,最终抛出错误。
  • InitiateAuth冗余参数干扰:CUSTOM_AUTH流无需传入password参数,该参数会触发Cognito默认密码验证流程,与自定义流冲突。
  • OTP存储异常:若CreateAuthChallenge未将OTP正确存入privateChallengeParameters,会导致VerifyAuthChallengeResponse中correctAnswer无效,但你已返回answerCorrect=true,此可能性较低,仍需确认CreateAuthChallenge逻辑。

2. Lambda响应需设置的特定字段

  • DefineAuthChallenge:
    验证成功时必须设置issueTokens=true和failAuthentication=false,告知Cognito结束认证并颁发令牌。示例逻辑:
    exports.handler = async (event) => {
      const session = event.request.session || [];
      const hasValidChallenge = session.some(attempt => 
        attempt.challengeName === 'CUSTOM_CHALLENGE' && attempt.challengeResult === true
      );
      
      if (hasValidChallenge) {
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
      } else {
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
      }
      return event;
    };
    
  • VerifyAuthChallengeResponse:只需正确设置event.response.answerCorrect=true/false,前提是correctAnswer来自CreateAuthChallenge存入的privateChallengeParameters.otp。

3. 测试用户创建时间的影响

自定义流实现前创建的用户不会直接导致问题,但需确认:

  • 用户未设置密码:若用户之前设置过密码,Cognito可能优先触发密码验证,与自定义流冲突。可通过控制台或API清除用户密码,确保仅通过自定义流认证。

关键修复步骤

  1. 移除InitiateAuth中的password参数:
    修改NestJS代码,删除冗余的password字段:
    async initiateAuth(phoneNumber: string): Promise<any> {
        const command = new InitiateAuthCommand({
          ClientId: this.clientId,
          AuthFlow: "CUSTOM_AUTH",
          AuthParameters: {
            USERNAME: phoneNumber
          },
        });
        // 其余代码不变
      }
    
  2. 完善DefineAuthChallenge逻辑:确保验证成功后触发令牌颁发,而非继续挑战。
  3. 确认CreateAuthChallenge的OTP存储:确保OTP正确存入privateChallengeParameters,示例:
    exports.handler = async (event) => {
      const otp = Math.floor(100000 + Math.random() * 900000).toString();
      // 发送WhatsApp OTP逻辑
      event.response.privateChallengeParameters = { otp };
      event.response.challengeMetadata = 'CUSTOM_CHALLENGE_OTP';
      return event;
    };
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 04:29:50