Cognito无密码认证:如何判定用户通过邮箱还是手机号登录
Cognito自定义认证双渠道验证码发送 登录标识获取方案
你遇到的是Cognito自定义认证流的经典已知限制:ClientMetadata参数确实仅会透传给Pre Signup、Pre Authentication、User Migration三类触发器,其余认证链路触发器默认拿不到InitiateAuth阶段传入的自定义参数,且事件对象不会直接返回用户输入的原始登录别名,仅会返回解析后的用户属性与内部用户ID。以下是三种生产环境验证过的可行方案,按改造成本从低到高排序:
方案1:Pre Authentication触发器转存登录标识(改造成本最低)
- 客户端调用
InitiateAuthCommand时,除必填的USERNAME等认证参数外,在ClientMetadata中额外传入loginChannel字段(值为sms/email,可在客户端先做格式校验判断:符合E.164手机号格式则传sms,包含@则传email)。 - 在Pre Authentication触发器中,直接从事件对象的
request.clientMetadata读取loginChannel值,调用管理员权限接口更新当前用户的自定义属性custom:last_login_channel(该属性仅配置管理员读写权限,禁止客户端访问,避免篡改)。 - 后续Create Auth Challenge触发器触发时,直接从
event.request.userAttributes中读取custom:last_login_channel的值,判断走短信还是邮件渠道发送验证码即可。 - 收尾逻辑:可在Verify Auth Challenge验证通过、签发Token的节点,清空该自定义属性的值,避免脏数据影响下次登录。
方案2:利用认证Session上下文透传(无外部依赖,纯Cognito原生能力)
Cognito自定义认证流中,event.request.session数组的内容会在整个认证生命周期内,在所有触发器之间透传,支持追加自定义字段,完全不需要依赖外部存储或用户属性:
- 客户端首次调用
InitiateAuthCommand发起自定义认证,正常传入用户输入的账号作为USERNAME参数。 - 首次触发Define Auth Challenge时,判断
event.request.session数组长度为0(即认证流程刚启动),直接返回自定义挑战CAPTURE_CHANNEL,设置issueTokens = false、failAuthentication = false,不需要携带任何挑战参数。 - 客户端收到
CAPTURE_CHANNEL挑战后,不需要用户做任何操作,静默调用RespondToAuthChallenge接口,在ChallengeResponses中传入之前用户输入的账号、客户端识别到的loginChannel参数即可,整个过程用户无感知。 - 第二次触发Define Auth Challenge时,即可从
event.request.challengeResponses中读取到loginChannel值,将其写入当前session最后一条记录的challengeMetadata字段(支持序列化JSON字符串存值),后续正常触发验证码发送挑战时,Create Auth Challenge触发器直接从session的challengeMetadata中读取渠道值即可。
注意:不要往session对象里存超过1KB的内容,否则会被Cognito截断,仅存必要的渠道标识即可。
方案3:登录页前置渠道判断(零后端改造)
如果你的登录页提供明确的登录渠道入口(比如分「短信验证码登录」「邮箱验证码登录」两个Tab),或账号格式无重叠(手机号统一为带国家码的E.164格式、邮箱必然包含@),不需要后端反推用户登录渠道:
- 客户端在用户输入账号后直接完成渠道判断,配合方案1的ClientMetadata传参逻辑,在InitiateAuth阶段直接把渠道值传给Pre Authentication触发器即可,不需要额外做会话流转或属性写入的兼容逻辑,后端仅需加不到10行代码就能完成转存。
避坑提示
不要尝试通过Cognito内置的validationData字段传参,该字段仅在SRP认证流的首轮透传,后续触发器无法读取;也不要将登录标识存在客户端本地缓存,多账号切换场景下极易出现串号、发错验证码的问题。
内容的提问来源于stack exchange,提问作者Tanuj Gupta
相关产品推荐
相关产品推荐

