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

能否在Cognito的CUSTOM_AUTH流中触发MFA挑战?无需密码如何实现?

问题:在Cognito的CUSTOM_AUTH流中触发MFA挑战(无需用户密码)

我们的应用使用Cognito进行用户认证与MFA处理,采用CUSTOM_AUTH流,通过defineauth、createauth和verifyauth Lambda实现了Cognito原生不支持的邮箱MFA。现需为部分操作实现额外MFA,计划根据用户配置触发对应MFA挑战,结合CUSTOM_CHALLENGE添加安全控制,但目前无法在CUSTOM_AUTH流中触发Cognito的MFA挑战。

测试场景与问题

简化测试用的createauth Lambda函数如下:

def lambda_handler(event, context):
    print("received event:", event)
    process_event(event)
    print("response event:", event)
    return event

def process_event(event):
    if len(event["request"]["session"]) == 0:
        event["response"]["issueTokens"] = False
        event["response"]["failAuthentication"] = False
        event["response"]["challengeName"] = "SOFTWARE_TOKEN_MFA"
        return

    s = event["request"]["session"][len(event["request"]["session"]) - 1]

    if s["challengeName"] == "SOFTWARE_TOKEN_MFA" and s["challengeResult"]:
        event["response"]["issueTokens"] = True
        event["response"]["failAuthentication"] = False
        return

    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = True

该函数假设用户已启用SOFTWARE_TOKEN_MFA,将其作为首个挑战,验证通过后发放令牌。

执行初始化认证命令:

aws cognito-idp initiate-auth --auth-flow CUSTOM_AUTH \
        --client-id <client-id> \
        --region ap-southeast-2 \
        --auth-parameters 'USERNAME=<username>'

返回结果符合预期:

{
    "ChallengeName": "SOFTWARE_TOKEN_MFA",
    "Session": "<session>",
    "ChallengeParameters": {
        "USERNAME": "<user-id>"
    }
}

但提交有效验证码(用户已正常配置MFA,可通过验证码登录)时:

aws cognito-idp respond-to-auth-challenge 
    --client-id <client-id>
    --region ap-southeast-2 
    --challenge-name SOFTWARE_TOKEN_MFA 
    --challenge-responses 'USERNAME=<user-id>,SOFTWARE_TOKEN_MFA_CODE=612345' 
    --session <session>

出现错误:

An error occurred (CodeMismatchException) when calling the RespondToAuthChallenge operation: Invalid code or auth state for the user.

现咨询:能否在Cognito的CUSTOM_AUTH流中触发MFA挑战?若可行,如何无需用户密码实现?


解答

1. 结论:可以实现,但不能直接返回SOFTWARE_TOKEN_MFA作为挑战类型

Cognito的CUSTOM_AUTH流中,所有挑战的验证逻辑完全由自定义Lambda接管,Cognito不会自动处理SOFTWARE_TOKEN_MFA这类原生挑战的校验。直接返回该挑战名会导致Cognito无法识别验证状态,从而抛出CodeMismatchException。

2. 无需用户密码的实现方案

核心思路是:先通过自定义挑战完成无密码身份确认(如邮箱/短信验证),再在Lambda中手动调用Cognito原生API验证MFA码,全程通过CUSTOM_AUTH流控制。

步骤1:调整Create Auth Challenge Lambda逻辑

该Lambda负责发起挑战,需分阶段处理无密码身份确认和MFA验证:

import boto3

cognito_idp = boto3.client('cognito-idp')

def lambda_handler(event, context):
    process_event(event)
    return event

def process_event(event):
    session = event["request"]["session"]
    username = event["request"]["userAttributes"]["sub"]
    user_pool_id = event["userPoolId"]

    # 第一阶段:首次会话,发起无密码身份确认挑战(示例为邮箱验证)
    if len(session) == 0:
        event["response"]["issueTokens"] = False
        event["response"]["failAuthentication"] = False
        event["response"]["challengeName"] = "CUSTOM_CHALLENGE"
        event["response"]["challengeParameters"] = {
            "CHALLENGE_TYPE": "EMAIL_VERIFICATION",
            "USERNAME": username
        }
        # 此处添加发送邮箱验证码的逻辑
        send_verification_email(username)
        return

    last_challenge = session[-1]

    # 第二阶段:无密码验证通过,检查用户是否启用原生MFA
    if last_challenge["challengeName"] == "CUSTOM_CHALLENGE" and last_challenge["challengeResult"]:
        try:
            user_data = cognito_idp.admin_get_user(
                UserPoolId=user_pool_id,
                Username=username
            )
            # 判断用户是否启用软件令牌MFA
            has_software_mfa = any(
                mfa["DeliveryMedium"] == "SOFTWARE_TOKEN" 
                for mfa in user_data.get("UserMFASettingList", [])
            )
            if has_software_mfa:
                # 发起MFA验证挑战
                event["response"]["issueTokens"] = False
                event["response"]["failAuthentication"] = False
                event["response"]["challengeName"] = "CUSTOM_CHALLENGE"
                event["response"]["challengeParameters"] = {
                    "CHALLENGE_TYPE": "SOFTWARE_TOKEN_MFA",
                    "USERNAME": username
                }
                return
            else:
                # 用户未启用MFA,直接发放令牌
                event["response"]["issueTokens"] = True
                event["response"]["failAuthentication"] = False
                return
        except Exception as e:
            print(f"获取用户信息失败:{str(e)}")
            event["response"]["failAuthentication"] = True
            return

    # 第三阶段:MFA验证结果处理
    if (last_challenge["challengeName"] == "CUSTOM_CHALLENGE" 
        and last_challenge["challengeParameters"]["CHALLENGE_TYPE"] == "SOFTWARE_TOKEN_MFA"):
        if last_challenge["challengeResult"]:
            event["response"]["issueTokens"] = True
            event["response"]["failAuthentication"] = False
        else:
            event["response"]["failAuthentication"] = True
        return

    # 默认情况:验证失败
    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = True

def send_verification_email(username):
    # 实现发送邮箱验证码的逻辑,比如调用SES或内部邮件服务
    pass

步骤2:调整Verify Auth Challenge Lambda逻辑

该Lambda负责验证用户提交的挑战答案,需分别处理邮箱验证和MFA验证:

import boto3

cognito_idp = boto3.client('cognito-idp')

def lambda_handler(event, context):
    challenge_type = event["request"]["challengeParameters"]["CHALLENGE_TYPE"]
    username = event["request"]["challengeParameters"]["USERNAME"]
    user_answer = event["request"]["challengeAnswer"]
    user_pool_id = event["userPoolId"]

    # 验证邮箱验证码
    if challenge_type == "EMAIL_VERIFICATION":
        is_valid = verify_email_code(username, user_answer)
        event["response"]["answerCorrect"] = is_valid
        return event

    # 验证软件令牌MFA码
    elif challenge_type == "SOFTWARE_TOKEN_MFA":
        try:
            cognito_idp.verify_software_token(
                UserPoolId=user_pool_id,
                Username=username,
                UserCode=user_answer,
                Session=event["request"]["session"]
            )
            event["response"]["answerCorrect"] = True
        except cognito_idp.exceptions.CodeMismatchException:
            event["response"]["answerCorrect"] = False
        except Exception as e:
            print(f"MFA验证失败:{str(e)}")
            event["response"]["answerCorrect"] = False
        return event

    # 默认验证失败
    event["response"]["answerCorrect"] = False
    return event

def verify_email_code(username, code):
    # 实现邮箱验证码验证逻辑,比如从缓存或数据库查询对比
    return code == "你的验证码"  # 示例逻辑

步骤3:配置Lambda权限

确保Create Auth Challenge和Verify Auth Challenge Lambda的IAM角色拥有以下权限:

  • cognito-idp:AdminGetUser
  • cognito-idp:VerifySoftwareToken
  • 若使用邮箱验证,需添加邮件服务权限(如ses:SendEmail)

3. 关键注意事项

  • 必须通过CUSTOM_CHALLENGE封装原生MFA验证,不能直接返回SOFTWARE_TOKEN_MFA作为挑战名
  • 无密码身份确认是前置步骤,确保用户身份合法后再触发MFA
  • 调用VerifySoftwareToken时,需传入正确的用户Session和用户池ID,否则会验证失败
  • 确保用户已在Cognito控制台或通过API启用了SOFTWARE_TOKEN_MFA

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 11:47:02