如何修复AWS Cognito Lambda触发器返回{####}而非正确OTP的问题?
解决AWS Cognito自定义消息Lambda无法获取实际OTP的问题
问题背景
我正在使用AWS Cognito实现用户认证,通过Lambda函数结合SendGrid发送验证邮件(因AWS SES生产模式申请被驳回,故采用此方案)。但遇到问题:在触发器中访问event.request.codeParameter时,返回的是{####}占位符而非实际的OTP验证码。
我的Lambda代码如下:
const sgMail = require('@sendgrid/mail'); require('dotenv').config(); sgMail.setApiKey(process.env.SENDGRID_API_KEY); exports.handler = async (event, context, callback) => { console.log("Received event:", JSON.stringify(event, null, 2)); const msg = { to: event.request.userAttributes.email, from: 'noreply@myapp.com', subject: 'Verify Your Email', text: `Please verify your email by entering the following code: ${event.request.codeParameter}`, html: `<strong>Please verify your email by entering the following code: ${event.request.codeParameter}</strong>` }; try { await sgMail.send(msg); callback(null, event); } catch (error) { console.error('Error sending email:', error); callback(error, null); } };
触发时收到的事件日志:
{ "version": "1", "region": "eu-north-1", "userPoolId": "String", "userName": "String", "callerContext": { "awsSdkVersion": "aws-sdk-unknown-unknown", "clientId": "String" }, "triggerSource": "CustomMessage_SignUp", "request": { "userAttributes": { "sub": "40e32520-....", "cognito:email_alias": "a@gmail.com", "email_verified": "false", "cognito:user_status": "UNCONFIRMED", "email": "a@gmail.com" }, "codeParameter": "{####}", "linkParameter": "{##Click Here##}", "usernameParameter": null }, "response": { "smsMessage": null, "emailMessage": null, "emailSubject": null } }
问题原因
{####}是AWS Cognito的内置占位符,并非真实的OTP验证码。Cognito不会将自动生成的OTP直接传递给CustomMessage触发器,这个占位符仅用于当你让Cognito代为发送邮件时,由Cognito自动替换为真实验证码。
由于你无法使用Cognito自带的邮件发送(SES申请被拒),需要自行生成、存储并验证OTP,具体方案如下:
解决方案步骤
1. 修改Lambda函数:生成OTP并存储到用户属性
在Lambda中生成自定义OTP,通过AWS SDK将其存储到用户的自定义属性中,再用SendGrid发送邮件。
修改后的代码示例:
const sgMail = require('@sendgrid/mail'); const AWS = require('aws-sdk'); require('dotenv').config(); sgMail.setApiKey(process.env.SENDGRID_API_KEY); const cognito = new AWS.CognitoIdentityServiceProvider(); exports.handler = async (event, context, callback) => { console.log("Received event:", JSON.stringify(event, null, 2)); // 生成6位随机OTP const otp = Math.floor(100000 + Math.random() * 900000).toString(); // 设置OTP过期时间(5分钟后) const expiresAt = Math.floor(Date.now() / 1000) + 300; try { // 将OTP和过期时间存储到用户自定义属性 await cognito.adminUpdateUserAttributes({ UserPoolId: event.userPoolId, Username: event.userName, UserAttributes: [ { Name: 'custom:verificationCode', Value: otp }, { Name: 'custom:codeExpiresAt', Value: expiresAt.toString() } ] }).promise(); // 发送邮件 const msg = { to: event.request.userAttributes.email, from: 'noreply@myapp.com', subject: 'Verify Your Email', text: `Please verify your email by entering the following code: ${otp}\nThis code will expire in 5 minutes.`, html: `<strong>Please verify your email by entering the following code: ${otp}</strong><br>This code will expire in 5 minutes.` }; await sgMail.send(msg); callback(null, event); } catch (error) { console.error('Error processing request:', error); callback(error, null); } };
2. 配置Lambda的IAM权限
给Lambda执行角色添加以下权限,允许其更新Cognito用户属性:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "cognito-idp:AdminUpdateUserAttributes", "Resource": "arn:aws:cognito-idp:YOUR_REGION:YOUR_ACCOUNT_ID:userpool/YOUR_USER_POOL_ID" } ] }
3. 配置自定义验证流程(用于校验OTP)
需要添加两个Cognito触发器来处理验证码验证:
- DefineAuthChallenge:定义验证流程逻辑,要求用户输入OTP
- VerifyAuthChallengeResponse:验证用户输入的OTP是否与存储的一致
DefineAuthChallenge触发器示例代码:
exports.handler = async (event) => { if (event.request.session.length === 0) { // 首次触发,要求用户输入OTP event.response.challengeName = 'CUSTOM_CHALLENGE'; event.response.challengeParameters = { email: event.request.userAttributes.email }; event.response.failAuthentication = false; event.response.issueTokens = false; } else if (event.request.session.slice(-1)[0].challengeResult === true) { // 验证成功,颁发令牌 event.response.failAuthentication = false; event.response.issueTokens = true; } else { // 验证失败 event.response.failAuthentication = true; event.response.issueTokens = false; } return event; };
VerifyAuthChallengeResponse触发器示例代码:
const AWS = require('aws-sdk'); const cognito = new AWS.CognitoIdentityServiceProvider(); exports.handler = async (event) => { const userInputOtp = event.request.challengeAnswer; try { // 获取用户存储的OTP和过期时间 const userData = await cognito.adminGetUser({ UserPoolId: event.userPoolId, Username: event.userName }).promise(); const verificationCodeAttr = userData.UserAttributes.find(attr => attr.Name === 'custom:verificationCode'); const expiresAtAttr = userData.UserAttributes.find(attr => attr.Name === 'custom:codeExpiresAt'); if (!verificationCodeAttr || !expiresAtAttr) { event.response.answerCorrect = false; return event; } const storedOtp = verificationCodeAttr.Value; const expiresAt = parseInt(expiresAtAttr.Value); const currentTime = Math.floor(Date.now() / 1000); // 验证OTP是否正确且未过期 if (userInputOtp === storedOtp && currentTime < expiresAt) { event.response.answerCorrect = true; // 验证成功后清除OTP属性 await cognito.adminUpdateUserAttributes({ UserPoolId: event.userPoolId, Username: event.userName, UserAttributes: [ { Name: 'custom:verificationCode', Value: '' }, { Name: 'custom:codeExpiresAt', Value: '' } ] }).promise(); } else { event.response.answerCorrect = false; } return event; } catch (error) { console.error('Error verifying OTP:', error); event.response.answerCorrect = false; return event; } };
4. 配置Cognito用户池
- 在用户池的自定义属性中添加
custom:verificationCode和custom:codeExpiresAt两个字符串类型属性。 - 在用户池的触发器中,设置:
CustomMessage_SignUp为你修改后的发送邮件Lambda。DefineAuthChallenge和VerifyAuthChallengeResponse为对应的验证Lambda。
说明
通过以上步骤,你将完全掌控OTP的生成、发送和验证流程,不再依赖Cognito自带的邮件发送功能,同时解决了无法获取真实OTP的问题。
内容的提问来源于stack exchange,提问作者iEmad00
相关产品推荐
相关产品推荐

