最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - How to Implement Multiple Cognito Custom Authentication Challenges for OTP Selection and Validation Using Lambda Trigge

programmeradmin6浏览0评论

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?

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?

Share Improve this question edited Mar 25 at 3:27 luk2302 57.3k24 gold badges104 silver badges149 bronze badges asked Mar 25 at 3:16 burns0907burns0907 1362 silver badges9 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

In DefineAuthChallenge, the initial request starts with a session length of 0. At this stage, we present the first challenge to determine the user’s preferred OTP delivery method (Email or SMS). Once this challenge is successfully completed, we proceed to the second challenge for OTP verification.

As shown, the session length increases after each challenge, allowing us to add multiple challenges as needed before issuing the final authentication tokens.

    if session_length == 0:
        # First authentication attempt, issue custom challenge
        event['response']['challengeName'] = 'CUSTOM_CHALLENGE'
        event['response']['issueTokens'] = False
        event['response']['failAuthentication'] = False

    elif session_length == 1 and session[-1].get('challengeResult') is True:
        # If the last challenge was successful, issue tokens
        event['response']['challengeName'] = 'CUSTOM_CHALLENGE'
        event['response']['issueTokens'] = False
        event['response']['failAuthentication'] = False
    
    elif session_length == 2 and session[-1].get('challengeResult') is True:
        # If the last challenge was successful, issue tokens
        event['response']['issueTokens'] = True
        event['response']['failAuthentication'] = False

    else:
        # If challenge failed, authentication should fail
        event['response']['issueTokens'] = False
        event['response']['failAuthentication'] = True

    print("Final response:", json.dumps(event['response'], indent=2))
    return event

In CreateAuthChallenge, we use the session length to determine which challenge. The client passes metadata (such as the user’s input from the UI) through clientMetadata, which helps shape and respond to each challenge appropriately.

if session_length == 0 and event['request']['challengeName'] == 'CUSTOM_CHALLENGE':
   # First challenge: Ask user how they want to receive OTP
   event['response']['publicChallengeParameters'] = {
            "question": "Do you want to receive the OTP via Email or Phone?"
        }
   event['response']['privateChallengeParameters'] = {"answer": "email_or_phone"}
        event['response']['challengeMetadata'] = 'SELECT_OTP_METHOD'

   return event


if session_length == 1 and event['request']['challengeName'] == 'CUSTOM_CHALLENGE':
   # Second challenge: Generate and send OTP
   delivery_method = event['request']['clientMetadata']['login_method']
   print("Delivery method:", delivery_method)

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

   if delivery_method == 'EMAIL_OTP':
   # send email 

   if delivery_method == 'SMS_OTP': 
   # send sms

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论