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

如何在AWS Cognito无密码流程中实现注册登录合一并确认后签发访问令牌

实现AWS Cognito注册/登录合一的无密码认证流程

核心思路

利用Cognito的自定义认证流(Custom Auth Flow),结合三个Lambda触发器,将用户注册、验证、登录合并为单一步骤:用户仅输入邮箱,系统自动判断是否为新用户,仅发送一次验证码,验证通过后直接返回访问令牌。


步骤1:配置Cognito用户池

  • 进入用户池控制台,在「认证流程」中启用自定义认证
  • 确保用户池已配置邮箱作为登录标识(可设置为用户名或别名),并开启无密码登录模式
  • 关闭用户池的「自动验证」和「确认后强制登录」等默认流程,避免重复发码

步骤2:创建三个关键Lambda触发器

需要为用户池按顺序配置以下三个Lambda触发器:

1. Define Auth Challenge(定义认证挑战)

该函数决定当前认证阶段的操作,核心逻辑是判断用户是否存在,新用户自动完成注册:

exports.handler = async (event) => {
    const { request, response } = event;
    
    // 检查用户是否存在
    const userExists = await checkUserExists(event.userPoolId, request.userAttributes.email);
    
    if (!userExists) {
        // 新用户:自动创建并启用用户
        await createAndEnableUser(event.userPoolId, request.userAttributes.email);
        response.challengeName = 'CUSTOM_CHALLENGE';
        response.challengeParameters = { USERNAME: request.userAttributes.email };
    } else if (request.session.length === 0) {
        // 已有用户:首次请求发起验证码挑战
        response.challengeName = 'CUSTOM_CHALLENGE';
        response.challengeParameters = { USERNAME: request.userAttributes.email };
    } else {
        // 验证阶段:检查最后一次挑战结果
        const lastChallenge = request.session[request.session.length - 1];
        if (lastChallenge.challengeName === 'CUSTOM_CHALLENGE' && lastChallenge.challengeResult) {
            response.issueTokens = true;
            response.failAuthentication = false;
        } else {
            response.issueTokens = false;
            response.failAuthentication = true;
        }
    }
    
    return event;
};

// 辅助函数:检查用户是否存在
async function checkUserExists(userPoolId, username) {
    const AWS = require('aws-sdk');
    const cognito = new AWS.CognitoIdentityServiceProvider();
    try {
        await cognito.adminGetUser({ UserPoolId: userPoolId, Username: username }).promise();
        return true;
    } catch (err) {
        return err.code !== 'UserNotFoundException';
    }
}

// 辅助函数:创建并启用新用户
async function createAndEnableUser(userPoolId, username) {
    const AWS = require('aws-sdk');
    const cognito = new AWS.CognitoIdentityServiceProvider();
    await cognito.adminCreateUser({
        UserPoolId: userPoolId,
        Username: username,
        UserAttributes: [{ Name: 'email', Value: username }],
        MessageAction: 'SUPPRESS' // 禁用Cognito默认欢迎邮件
    }).promise();
    await cognito.adminEnableUser({ UserPoolId: userPoolId, Username: username }).promise();
}

2. Create Auth Challenge(生成并发送验证码)

该函数生成随机验证码并发送到用户邮箱:

exports.handler = async (event) => {
    const { request, response } = event;
    
    // 生成6位随机验证码
    const verificationCode = Math.floor(100000 + Math.random() * 900000).toString();
    
    // 发送验证码(替换为你的SES或邮件服务配置)
    await sendVerificationEmail(request.userAttributes.email, verificationCode);
    
    // 将验证码存入私有会话,供后续验证使用
    response.privateChallengeParameters = { verificationCode };
    response.challengeMetadata = 'CUSTOM_VERIFICATION_CODE';
    
    return event;
};

// 辅助函数:发送验证码邮件
async function sendVerificationEmail(email, code) {
    const AWS = require('aws-sdk');
    const ses = new AWS.SES();
    await ses.sendEmail({
        Source: 'your-verification-email@example.com',
        Destination: { ToAddresses: [email] },
        Message: {
            Subject: { Data: '登录/注册验证码' },
            Body: { Text: { Data: `你的验证码是:${code}` } }
        }
    }).promise();
}

3. Verify Auth Challenge Response(验证验证码)

该函数对比用户输入的验证码与会话中存储的验证码:

exports.handler = async (event) => {
    const { request, response } = event;
    const userInputCode = request.challengeAnswer;
    const correctCode = request.privateChallengeParameters.verificationCode;
    
    response.answerCorrect = userInputCode === correctCode;
    
    return event;
};

步骤3:前端调用流程

前端只需两次API调用即可完成认证:

  1. 首次调用触发自定义认证流:
const cognito = new AWS.CognitoIdentityServiceProvider();
const initiateResponse = await cognito.initiateAuth({
    AuthFlow: 'CUSTOM_AUTH',
    ClientId: 'your-cognito-client-id',
    AuthParameters: { USERNAME: 'user-input-email@example.com' }
}).promise();
  1. 用户输入验证码后,完成验证并获取令牌:
const challengeResponse = await cognito.respondToAuthChallenge({
    ClientId: 'your-cognito-client-id',
    ChallengeName: 'CUSTOM_CHALLENGE',
    Session: initiateResponse.Session,
    ChallengeResponses: {
        USERNAME: 'user-input-email@example.com',
        ANSWER: 'user-input-verification-code'
    }
}).promise();
// 获取令牌
const accessToken = challengeResponse.AuthenticationResult.AccessToken;
const idToken = challengeResponse.AuthenticationResult.IdToken;

关键注意事项

  • 给Lambda函数附加AmazonCognitoPowerUser策略或自定义权限,需包含cognito-idp:AdminGetUser、cognito-idp:AdminCreateUser、cognito-idp:AdminEnableUser等权限
  • 配置SES域名验证,避免验证码邮件进入垃圾箱
  • 可在Define Auth Challenge中添加请求频率限制,防止恶意刷验证码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 11:17:10