基于C#的AWS Cognito服务器端CUSTOM_AUTH流实现问题
解决方案:服务器端实现AWS Cognito CUSTOM_AUTH邮箱MFA流程
核心调整方向
你的问题主要出在CUSTOM_AUTH流程的参数传递、Lambda触发器逻辑完整性,以及挑战响应的参数规范上。以下是分步修正方案:
1. 修正后端CUSTOM_AUTH初始化请求
将原认证方法改为CUSTOM_AUTH模式,同时传入用户名和密码供Lambda验证:
public async Task<AdminInitiateAuthResponse> InitiateCustomAuth(string username, string password) { var authRequest = new AdminInitiateAuthRequest { UserPoolId = this._poolId, ClientId = this._clientId, AuthFlow = AuthFlowType.CUSTOM_AUTH, AuthParameters = new Dictionary<string, string> { {"USERNAME", username}, {"PASSWORD", password} // 传递密码到Lambda做验证 } }; return await this._client.AdminInitiateAuthAsync(authRequest); }
2. 完善三个Lambda触发器(缺一不可)
CUSTOM_AUTH需要三个Lambda触发器协作:DefineAuthChallenge(控制挑战流程)、CreateAuthChallenge(生成并发送OTP)、VerifyAuthChallengeResponse(验证OTP)。
2.1 DefineAuthChallenge Lambda(修正逻辑错误)
处理密码验证、挑战流转和失败次数限制:
import AWS from 'aws-sdk'; const cognito = new AWS.CognitoIdentityServiceProvider(); export const handler = async (event) => { const session = event.request.session || []; // 避免session为null // 第一次请求:验证用户名密码 if (session.length === 0) { try { // 调用ADMIN_NO_SRP_AUTH验证密码 await cognito.adminInitiateAuth({ UserPoolId: event.userPoolId, ClientId: event.callerContext.clientId, AuthFlow: "ADMIN_NO_SRP_AUTH", AuthParameters: { USERNAME: event.userName, PASSWORD: event.request.challengeParameters.PASSWORD } }).promise(); // 密码验证通过,触发OTP挑战 event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = "CUSTOM_CHALLENGE"; } catch (err) { // 密码错误,拒绝认证 event.response.issueTokens = false; event.response.failAuthentication = true; } } // 已发起OTP挑战,等待用户输入 else if (session.length === 1 && session[0].challengeName === "CUSTOM_CHALLENGE" && !session[0].challengeResult) { event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = "CUSTOM_CHALLENGE"; } // OTP验证通过,颁发令牌 else if (session.length > 0 && session.slice(-1)[0].challengeName === "CUSTOM_CHALLENGE" && session.slice(-1)[0].challengeResult) { event.response.issueTokens = true; event.response.failAuthentication = false; } // 超过3次OTP输入失败,锁定认证 else if (session.length >= 3 && session.slice(-1)[0].challengeName === "CUSTOM_CHALLENGE" && !session.slice(-1)[0].challengeResult) { event.response.issueTokens = false; event.response.failAuthentication = true; } // 其他情况,继续OTP挑战 else { event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = "CUSTOM_CHALLENGE"; } return event; };
2.2 CreateAuthChallenge Lambda(生成并发送OTP)
生成6位验证码,通过SES发送到用户邮箱:
import AWS from 'aws-sdk'; const ses = new AWS.SES(); export const handler = async (event) => { if (event.request.challengeName === "CUSTOM_CHALLENGE") { // 生成6位随机OTP const otp = Math.floor(100000 + Math.random() * 900000).toString(); // 存储OTP到私有参数,供后续验证使用 event.response.privateChallengeParameters = { otp }; // 发送邮件(替换为你的已验证发件邮箱) await ses.sendEmail({ Source: "your-verified-email@example.com", Destination: { ToAddresses: [event.request.userAttributes.email] }, Message: { Subject: { Data: "登录验证码" }, Body: { Text: { Data: `你的登录验证码是:${otp}` } } } }).promise(); // 给客户端返回挑战标识 event.response.challengeMetadata = "EMAIL_OTP_CHALLENGE"; } return event; };
2.3 VerifyAuthChallengeResponse Lambda(验证OTP)
对比用户输入的验证码与存储的OTP:
export const handler = async (event) => { if (event.request.challengeName === "CUSTOM_CHALLENGE") { const expectedOtp = event.request.privateChallengeParameters.otp; const userInputOtp = event.request.challengeAnswer; // 验证验证码是否匹配 event.response.answerCorrect = expectedOtp === userInputOtp; } return event; };
3. 修正后端挑战响应方法
调整挑战响应的参数,使用Cognito规范的ANSWER键传递验证码,并包含用户名:
public async Task<AdminRespondToAuthChallengeResponse> RespondToCustomChallenge(string username, string otp, string session) { return await this._client.AdminRespondToAuthChallengeAsync(new AdminRespondToAuthChallengeRequest() { ChallengeName = ChallengeNameType.CUSTOM_CHALLENGE, ClientId = this._clientId, UserPoolId = this._poolId, ChallengeResponses = new Dictionary<string, string> { { "USERNAME", username }, { "ANSWER", otp } // Cognito默认用ANSWER作为验证码参数键 }, Session = session }); }
4. 端到端流程适配
- 前端提交用户名密码到后端,后端调用
InitiateCustomAuth - 如果返回
ChallengeName=CUSTOM_CHALLENGE,后端将Session返回给前端,前端渲染验证码输入框 - 用户输入验证码后,前端提交验证码和
Session到后端,后端调用RespondToCustomChallenge - 验证通过后,后端用返回的
AuthenticationResult生成自定义令牌,返回给前端
关键注意事项
- 确保Lambda角色拥有
cognito-idp:AdminInitiateAuth和ses:SendEmail的权限 - 用户池App客户端需启用
CUSTOM_AUTH认证流程 - 确保用户邮箱已在Cognito中验证,否则无法发送OTP
内容的提问来源于stack exchange,提问作者Neophyte
相关产品推荐
相关产品推荐

