能否让用户选择通过邮箱或SMS接收AWS Cognito验证码?
实现AWS Cognito邮箱/SMS验证码二选一验证方案
前提准备
- 确保Cognito用户池已开启邮箱和短信两种验证方式,且用户注册时已收集邮箱和手机号信息(或支持后续补充)
- 配置Amplify Auth模块时,启用对应验证通道
核心实现步骤
1. 配置自定义认证流程(Lambda触发器)
Cognito默认按预设优先级发送验证码,要实现用户自主选择,需通过三个Lambda触发器干预流程:
- Define Auth Challenge:判断认证阶段,首次请求时返回「选择验证通道」的挑战;用户选择通道后,触发「验证码验证」挑战;最后验证验证码是否正确,决定是否发放令牌
- Create Auth Challenge:根据用户选择的通道(邮箱/SMS)生成验证码,调用Cognito API发送,并临时存储验证码用于后续校验
- Verify Auth Challenge Response:比对用户输入的验证码与存储的验证码,返回验证结果
2. React前端交互实现
在登录/验证页面添加通道选择UI,配合Amplify Auth API完成流程:
- 用户输入账号后调用
Auth.signIn(username)触发初始认证 - 收到Cognito返回的
SELECT_CHALLENGE_CHANNEL挑战时,展示邮箱/SMS选择组件 - 用户选择通道后,调用
Auth.sendCustomChallengeAnswer(user, selectedChannel)传递选择给Lambda - 等待用户输入验证码后,再次调用
Auth.sendCustomChallengeAnswer(user, verificationCode)完成验证
代码示例
React前端代码
import { Auth } from 'aws-amplify'; const handleSignIn = async (username) => { try { const user = await Auth.signIn(username); // 判断是否需要选择验证通道 if (user.challengeName === 'SELECT_CHALLENGE_CHANNEL') { // 自定义方法:展示通道选择弹窗,获取用户选择 const selectedChannel = await showChannelSelectModal(); await Auth.sendCustomChallengeAnswer(user, selectedChannel); // 自定义方法:展示验证码输入框,获取用户输入 const verificationCode = await showVerificationCodeInput(); const result = await Auth.sendCustomChallengeAnswer(user, verificationCode); console.log('登录成功', result); } } catch (err) { console.error('认证失败', err); } };
Define Auth Challenge Lambda触发器
exports.handler = async (event) => { const { request, response } = event; if (request.session.length === 0) { // 首次认证:要求用户选择验证通道 response.challengeName = 'SELECT_CHALLENGE_CHANNEL'; response.challengeParameters = { username: request.userName }; response.failAuthentication = false; response.issueTokens = false; } else if (request.session.length === 1 && request.challengeAnswer) { // 用户已选通道:发起验证码验证挑战 response.challengeName = 'VERIFY_CODE'; response.challengeParameters = { selectedChannel: request.challengeAnswer, username: request.userName }; response.failAuthentication = false; response.issueTokens = false; } else if (request.session.length === 2 && request.challengeAnswer) { // 验证验证码:需结合Create Auth Challenge中存储的验证码判断 const isValid = verifyCode(request.userName, request.challengeAnswer); response.issueTokens = isValid; response.failAuthentication = !isValid; } return event; };
Create Auth Challenge Lambda触发器
const AWS = require('aws-sdk'); const cognito = new AWS.CognitoIdentityServiceProvider(); exports.handler = async (event) => { const { request, response } = event; if (request.challengeName === 'VERIFY_CODE') { const { selectedChannel, username } = request.challengeParameters; const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); // 自定义方法:将验证码存储到DynamoDB或用户临时属性 await storeVerificationCode(username, verificationCode); // 根据选择的通道发送验证码 if (selectedChannel === 'email') { await cognito.adminUpdateUserAttributes({ UserPoolId: event.userPoolId, Username: username, UserAttributes: [{ Name: 'email_verified', Value: 'false' }] }).promise(); await cognito.adminSendUserConfirmationCode({ UserPoolId: event.userPoolId, Username: username, ClientId: event.callerContext.clientId, UsernameAttribute: 'email' }).promise(); } else if (selectedChannel === 'sms') { await cognito.adminUpdateUserAttributes({ UserPoolId: event.userPoolId, Username: username, UserAttributes: [{ Name: 'phone_number_verified', Value: 'false' }] }).promise(); await cognito.adminSendUserConfirmationCode({ UserPoolId: event.userPoolId, Username: username, ClientId: event.callerContext.clientId, UsernameAttribute: 'phone_number' }).promise(); } response.privateChallengeParameters = { verificationCode }; response.challengeMetadata = 'CODE_VERIFICATION_CHALLENGE'; } return event; };
注意事项
- 确保Lambda触发器拥有调用Cognito API、访问验证码存储服务(如DynamoDB)的权限
- 用户必须已在Cognito中留存邮箱和手机号,否则无法选择对应通道
- 可在注册流程中强制用户填写双渠道信息,或提供信息补充入口
内容的提问来源于stack exchange,提问作者Paúl
相关产品推荐
相关产品推荐

