[Python] How to Implement Multiple Cognito Custom Authentication Challenges for OTP Selection...

Discussão em 'Python' iniciado por Stack, Março 25, 2025.

  1. Stack

    Stack Membro Participativo

    I am working on implementing a passwordless authentication flow using Amazon Cognito Custom Authentication Challenges. I have three Lambda triggers set up: DefineAuthChallenge, CreateAuthChallenge, and VerifyAuthChallenge.

    Here’s what I want to achieve:

    1. Ask the user how they want to receive the OTP (via email or SMS). Since Cognito does not pass user input metadata to Lambda, I’m attempting to ask the user to select the delivery method in the first challenge, and then pass that selection as metadata to the next challenges.

    2. Generate and validate the OTP based on the method selected in the first step, and grant the user access if the OTP is correct.

    What Works:

    If I have only one challenge (generate OTP and validate it), everything works fine. The OTP is sent, and if the user enters the correct one, they are granted access.

    The Issue: When trying to implement multiple challenges:


    • On first call session is 0 so it calls challenge to select the OTP delivery method but doesn’t trigger the CreateAuthChallenge Lambda for OTP generation.


    • There’s also errors sometimes in the response saying it’s an unrecognized Lambda output or Local storage is missing an ID Token, Please authenticate cognito custom auth

    DefineAuthLambda

    import json

    def lambda_handler(event, context):
    print("EVENT request:", event["request"])

    session = event["request"]["session"]

    # First challenge: Ask the user how they want to receive the OTP
    if len(session) == 0:
    event["response"]["challengeName"] = "CUSTOM_CHALLENGE"
    event["response"]["challengeMetadata"] = "SELECT_DELIVERY_METHOD" # Metadata to show the selection UI
    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = False
    event["response"]["publicChallengeParameters"] = {
    "question": "How would you like to receive the OTP? (email or sms)"
    }

    # Second challenge: After the user selects delivery method, send OTP
    elif len(session) == 1 and session[0]["challengeName"] == "CUSTOM_CHALLENGE" and session[0]["challengeResult"]:
    event["response"]["challengeName"] = "CUSTOM_CHALLENGE"
    event["response"]["challengeMetadata"] = "VERIFY_OTP" # Metadata for verifying OTP
    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = False
    event["response"]["publicChallengeParameters"] = {
    "otpPrompt": "Enter the OTP sent to your email or phone"
    }

    # Third challenge: If OTP is correct, issue tokens
    elif len(session) == 2 and session[1]["challengeName"] == "CUSTOM_CHALLENGE" and session[1]["challengeResult"]:
    event["response"]["issueTokens"] = True
    event["response"]["failAuthentication"] = False

    # Failure case: If authentication fails
    else:
    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = True

    print("EVENT response:", event["response"])
    return event


    CreateAuthLambda

    import json
    import random

    def lambda_handler(event, context):
    print("EVENT request:", event["request"])

    session = event["request"]["session"]

    # First challenge: Ask the user how they want to receive the OTP
    if len(session) == 0:
    event["response"]["challengeName"] = "CUSTOM_CHALLENGE"
    event["response"]["challengeMetadata"] = "SELECT_DELIVERY_METHOD" # This indicates the frontend to display method selection
    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = False
    event["response"]["publicChallengeParameters"] = {
    "question": "How would you like to receive the OTP? (email or sms)"
    }

    # Second challenge: After the user selects delivery method, send OTP
    elif len(session) == 1 and session[0]["challengeName"] == "CUSTOM_CHALLENGE" and session[0]["challengeResult"]:
    # Get the delivery method selected by the user in the UI (email or sms)
    selected_method = event["request"]["challengeAnswer"]

    # Generate a random 8-digit OTP
    otp = str(random.randint(10000000, 99999999))

    event["response"]["challengeName"] = "CUSTOM_CHALLENGE"
    event["response"]["challengeMetadata"] = "VERIFY_OTP" # Indicating the frontend to ask for OTP verification
    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = False

    # Store OTP and the selected delivery method in privateChallengeParameters
    event["response"]["privateChallengeParameters"] = {
    "otp": otp,
    "deliveryMethod": selected_method # Store selected method (either email or sms)
    }

    # Simulate sending OTP to the selected method (email or sms)
    print(f"Sending OTP {otp} via {selected_method}")

    # Third challenge: If OTP is correct, issue tokens
    elif len(session) == 2 and session[1]["challengeName"] == "CUSTOM_CHALLENGE" and session[1]["challengeResult"]:
    event["response"]["issueTokens"] = True
    event["response"]["failAuthentication"] = False

    # Failure case: If authentication fails
    else:
    event["response"]["issueTokens"] = False
    event["response"]["failAuthentication"] = True

    print("EVENT response:", event["response"])
    return event


    VerifyAuthLambda

    def lambda_handler(event, context):
    print("EVENT request:", event["request"])

    challenge_name = event["request"]["challengeName"]
    user_answer = event["request"]["challengeAnswer"]
    private_params = event["request"]["privateChallengeParameters"]

    # If the challenge is for OTP delivery method selection (first challenge)
    if challenge_name == "CUSTOM_CHALLENGE":
    if event["request"]["challengeMetadata"] == "CHOOSE_OTP_DELIVERY":
    # No OTP to verify yet, just handle the user's delivery method choice
    selected_method = user_answer # 'email' or 'sms' (based on the user's choice)
    event["response"]["answerCorrect"] = True # Mark as correct, as it's just delivery method selection

    # If the challenge is for OTP verification (second challenge)
    elif event["request"]["challengeMetadata"] == "VERIFY_OTP":
    expected_otp = private_params["otp"] # OTP sent to the user (either email or SMS)
    event["response"]["answerCorrect"] = (user_answer == expected_otp)

    print("EVENT response:", event["response"])
    return event


    How can I present both challanges to the user without errors?

    Continue reading...

Compartilhe esta Página