AWS Cognito无密码自定义认证流返回‘用户名或密码错误’问题排查
场景描述
基于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响应属性无拼写错误
需要解答的问题
- 为何验证成功仍返回该错误?
- Lambda响应需设置哪些特定字段以确认认证成功?
- 是否因测试用户为自定义流实现前创建导致问题?
附相关代码
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清除用户密码,确保仅通过自定义流认证。
关键修复步骤
- 移除InitiateAuth中的password参数:
修改NestJS代码,删除冗余的password字段:async initiateAuth(phoneNumber: string): Promise<any> { const command = new InitiateAuthCommand({ ClientId: this.clientId, AuthFlow: "CUSTOM_AUTH", AuthParameters: { USERNAME: phoneNumber }, }); // 其余代码不变 } - 完善DefineAuthChallenge逻辑:确保验证成功后触发令牌颁发,而非继续挑战。
- 确认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
相关产品推荐
相关产品推荐

