如何向AWS Cognito createAuthChallenge触发器传递自定义数据
AWS Cognito自定义魔链登录传参问题解决方案
问题场景
使用AWS Cognito搭配AWS SES搭建自定义认证流程,实现魔链(Magic Link)登录:用户发起登录时,系统向用户邮箱发送携带令牌的登录链接。需求为根据用户来源自定义邮件内的跳转路径,例如部分用户跳转至https://mysite/foo?<token>,另一部分用户跳转至https://mysite/bar?<token>。
当前卡点为无法向Cognito认证触发器传递自定义变量。已配置CUSTOM_AUTH认证流,全程使用@aws-sdk/client-cognito-identity-provider原生SDK调用initiateAuthCommand发起认证请求,不希望引入Amplify框架。
现有实现代码
发起认证请求代码
import { CognitoIdentityProviderClient, InitiateAuthCommand } from '@aws-sdk/client-cognito-identity-provider' const poolConfig = { accessKeyId: process.env.AWS_APIKEY, secretAccessKey: process.env.AWS_SECRETKEY, region: process.env.AWS_REGION, poolId: process.env.AWS_COGNITO_POOL_ID, poolClientId: process.env.AWS_COGNITO_POOL_CLIENT_ID } const provider = new CognitoIdentityProviderClient(poolConfig) const authInput = { AuthFlow: "CUSTOM_AUTH", AuthParameters : { USERNAME: "test@mydomain.com", }, ClientId: process.env.AWS_COGNITO_POOL_CLIENT_ID } const signinCommand = new InitiateAuthCommand(authInput) try { const res = await provider.send(signinCommand) console.log('Signin success. Result: ', res) } catch (e) { console.log('Signin fail. Error: ', e) }
createAuthChallenge触发器代码
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2"; import { randomDigits } from 'crypto-secure-random-digit'; const config = { accessKeyId: process.env.AWS_APIKEY, secretAccessKey: process.env.AWS_SECRETKEY, region: process.env.AWS_REGION, } const client = new SESv2Client(config); export const handler = async (event) => { let secretLoginCode; if (!event.request.session || !event.request.session.length) { secretLoginCode = randomDigits(10).join(''); // 卡点:无法在此处获取传入的自定义变量(例如originUrl) await sendEmail(event.request.userAttributes.email, originUrl, secretLoginCode); } else { const previousChallenge = event.request.session.slice(-1)[0]; secretLoginCode = previousChallenge.challengeMetadata.match(/CODE-(\d*)/)[1]; } event.response.publicChallengeParameters = { email: event.request.userAttributes.email }; event.response.privateChallengeParameters = { secretLoginCode }; event.response.challengeMetadata = `CODE-${secretLoginCode}`; return event; }; async function sendEmail(emailAddress, originUrl, secretLoginCode) { var params = { Destination: { ToAddresses: [ emailAddress ] }, Content: { Simple: { Body: { Html: { Charset: "UTF-8", Data: `<p>Your link to connect is : <a href="${originUrl}/register?t=${secretLoginCode}">here</a></p>` }, }, Subject: { Charset: 'UTF-8', Data: 'Test email' } } }, FromEmailAddress: 'fromAddress@mydomain.com', }; const command = new SendEmailCommand(params); await client.send(command); }
已验证无效的方案
- 开启应用客户端
EnablePropagateAdditionalUserContextData参数:该参数用于自适应认证场景下传递设备指纹数据,必须开通Cognito高级安全付费功能才能使用,既无法独立完成账单审批,为传递简单自定义参数开通付费功能也不具备合理性。 - 调用接口时传入
ClientMetadata参数:InitiateAuth接口传入的ClientMetadata仅会传递给预注册、预认证、用户迁移类触发器,不会传递给自定义认证流的createAuthChallenge触发器,实测无法取值。
可行实现方案
核心逻辑:利用自定义认证流的DefineAuthChallenge触发器做参数中转,将AuthParameters中传入的自定义参数通过会话内部的challengeMetadata字段流转到createAuthChallenge触发器,全程无需开通付费功能,无需引入额外框架。
- 修改发起认证请求的参数,将自定义字段直接加入
AuthParameters对象:
const authInput = { AuthFlow: "CUSTOM_AUTH", AuthParameters : { USERNAME: "test@mydomain.com", originUrl: "https://mysite/foo" // 直接传入需要的自定义路径参数 }, ClientId: process.env.AWS_COGNITO_POOL_CLIENT_ID }
- 新增/修改
DefineAuthChallenge触发器,将请求中携带的自定义参数序列化后存入challengeMetadata,流转给后续触发器:
export const handler = async (event) => { // 首次发起认证无会话记录 if (!event.request.session || event.request.session.length === 0) { event.response.challengeName = "CUSTOM_CHALLENGE"; event.response.failAuthentication = false; event.response.issueTokens = false; // 从validationData中取出AuthParameters传入的自定义参数,序列化后存入challengeMetadata const customParams = encodeURIComponent(JSON.stringify({ originUrl: event.request.validationData?.originUrl || "https://mysite/default" })) event.response.challengeMetadata = customParams; } else { // 校验挑战答案逻辑和原有逻辑保持一致 const lastChallenge = event.request.session.slice(-1)[0]; event.response.issueTokens = lastChallenge.challengeResult === true; event.response.failAuthentication = lastChallenge.challengeResult !== true; } return event; }
- 修改
createAuthChallenge触发器,从传入的challengeMetadata中解析自定义参数,即可在发邮件时使用:
export const handler = async (event) => { let secretLoginCode; let originUrl = "https://mysite/default"; // 配置默认兜底路径 if (!event.request.session || !event.request.session.length) { secretLoginCode = randomDigits(10).join(''); // 从上游传过来的challengeMetadata解析自定义参数 if (event.request.challengeMetadata) { const customParams = JSON.parse(decodeURIComponent(event.request.challengeMetadata)); originUrl = customParams.originUrl; } await sendEmail(event.request.userAttributes.email, originUrl, secretLoginCode); } else { const previousChallenge = event.request.session.slice(-1)[0]; secretLoginCode = previousChallenge.challengeMetadata.match(/CODE-(\d*)/)[1]; } event.response.publicChallengeParameters = { email: event.request.userAttributes.email }; event.response.privateChallengeParameters = { secretLoginCode }; event.response.challengeMetadata = `CODE-${secretLoginCode}`; return event; };
注意事项
challengeMetadata为Cognito会话内部流转字段,不会暴露给前端,可安全存储跳转路径这类非敏感数据,不要存入密钥类敏感信息。publicChallengeParameters会直接返回给前端调用方,不要存放敏感数据。
内容的提问来源于stack exchange,提问作者antoine david
相关产品推荐
相关产品推荐

