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

如何修复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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 14:20:01