Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Authentication Flow for Email-Based MFA Failing to Trigger CreateAuthChallenge #14067

Open
4 tasks done
Mustafa-Algoace01 opened this issue Dec 11, 2024 · 10 comments
Open
4 tasks done
Assignees
Labels
Auth Related to Auth components/category question General question React Native React Native related issue

Comments

@Mustafa-Algoace01
Copy link

Mustafa-Algoace01 commented Dec 11, 2024

Before creating a new issue, please confirm:

On which framework/platform are you having an issue?

React Native

Which UI component?

Other

How is your app built?

npx react-native init YourProjectName

What browsers are you seeing the problem on?

iOS (React Native), Android (React Native)

Which region are you seeing the problem in?

us-east-2

Please describe your bug.

We are implementing a custom authentication flow using AWS Cognito with email-based MFA. While the DefineAuthChallenge Lambda function triggers as expected and responds with a CUSTOM_CHALLENGE, the CreateAuthChallenge Lambda function does not execute. This results in the challenge not being generated (e.g., create logs and otp show in log). Below are the details of the implementation and issues encountered.

What's the expected behaviour?

After the DefineAuthChallenge indicates a CUSTOM_CHALLENGE, the CreateAuthChallenge function should:

Generate the custom challenge (e.g., a 6-digit OTP).
Send the OTP via email to the user.
Store the OTP in private challenge parameters.

and verify otp in VerifyAuthChallenge

Help us reproduce the bug!

Configure AWS Cognito user pool with Lambda triggers for custom authentication:
DefineAuthChallenge
CreateAuthChallenge
VerifyAuthChallengeResponse
Set up permissions for the Lambda functions (IAM roles are correctly configured).
Sign in with the USER_SRP_AUTH flow using the AWS SDK.
Observe that the DefineAuthChallenge trigger runs, but the CreateAuthChallenge trigger does not execute.

Code Snippet

// Put your code below this line.
**DEFINE AUTH CHALLENGE**

export const handler = async (event) => {
 console.log('event', JSON.stringify(event, null, 2));  // More detailed logging for event
    console.log('event.request', JSON.stringify(event.request, null, 2));  // Log request parameters
    if (event.request.session.length == 1 && event.request.session[0].challengeName == 'SRP_A') {
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';

        // Pass the USER_ID_FOR_SRP to the next challenge
        event.response.challengeParameters = {
            USER_ID_FOR_SRP: event.request.userAttributes.sub,  // Ensure to pass the user id or any relevant attribute
        };

    } else if (
        event.request.session.length == 2 &&
        event.request.session[1].challengeName == 'CUSTOM_CHALLENGE' &&
        event.request.session[1].challengeResult == true
    ) {
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
    } else {
        event.response.issueTokens = false;
        event.response.failAuthentication = true;
    }

    console.log('event return', JSON.stringify(event, null, 2));  // Log request parameters
    return event;
};

**CREATE AUTH CHALLENGE**
export const handler = async (event) => {
    console.log("event",event)
    if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
        // Generate a 6-digit OTP
        const otp = Math.floor(100000 + Math.random() * 900000).toString();
        const email = event.request.userAttributes.email;

        // Store OTP as private challenge parameter
        event.response.privateChallengeParameters = { otp };
        event.response.challengeMetadata = 'CUSTOM_CHALLENGE';
console.log('check, otp',otp)
        // Send OTP via email
        // await sendEmail(email, otp);
    }
    return event;
};

siginIn function

const {isSignedIn, nextStep} = await signIn({
username: username,
password: password,
options: {authFlowType: 'CUSTOM_WITH_SRP'},
});

Console log output

No response

Additional information and screens

Screenshot 2024-12-11 at 5 33 45 PM
hots

No response

@github-actions github-actions bot added pending-triage Issue is pending triage pending-maintainer-response Issue is pending a response from the Amplify team. labels Dec 11, 2024
@cwomack
Copy link
Member

cwomack commented Dec 11, 2024

Hey, @Mustafa-Algoace01 👋. Sorry to hear you're running into this, but we'll transfer this over the the amplify-js repo for better assistance. It looks like you're using the standard Auth API's rather than the Authenticator or UI Components here.

@cwomack cwomack transferred this issue from aws-amplify/amplify-ui Dec 11, 2024
@cwomack cwomack added Auth Related to Auth components/category React Native React Native related issue and removed pending-maintainer-response Issue is pending a response from the Amplify team. labels Dec 11, 2024
@cwomack
Copy link
Member

cwomack commented Dec 11, 2024

@Mustafa-Algoace01, can you also share the code for the VerifyAuthChallengeResponse? It looks like the issue might be that you're missing the event.response.privateChallengeParameters.answer within your createAuthChallenge lambda as seen here. Can you see if including this resolves the issue?

Just so you're aware, we also recently released support for email MFA out of the box. You can read more about that in the MFA Auth section here, but not sure if you had implemented this yourself before we released this feature!

@cwomack cwomack added question General question pending-community-response Issue is pending a response from the author or community. and removed pending-triage Issue is pending triage labels Dec 11, 2024
@Mustafa-Algoace01
Copy link
Author

Mustafa-Algoace01 commented Dec 12, 2024

@cwomack I am trying to implement MFA using a third-party email service instead of AWS SES by integrating a custom authentication flow with Cognito. However, I am encountering the following issues:

If I remove the following line from the DefineAuthChallenge Lambda function, I get the error:

// Pass the USER_ID_FOR_SRP to the next challenge
event.response.challengeParameters = {
USER_ID_FOR_SRP: event.request.userAttributes.sub, // Ensure to pass the user id or any relevant attribute
};
Error:
USER_ID_FOR_SRP was not found in challengeParameters

If I add the above line, it throws an error:
unrecognizable lambda output

The VerifyAuthChallengeResponse Lambda function is not being triggered during the flow. Below is my VerifyAuthChallengeResponse function for reference:

// Lambda handler for VerifyAuthChallengeResponse
export const handler = async (event) => {
    console.log("Verify auth lambda function called");

    // Retrieve OTP from privateChallengeParameters (stored in previous lambda)
    const otp = event.request.privateChallengeParameters?.otp;

    // Compare entered OTP with generated OTP
    if (event.request.challengeAnswer === otp) {
        console.log("OTP verified successfully");

        // Return successful authentication result
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
        return "event";
    } else {
        console.log("OTP verification failed");

        // Return failure to authentication process
        event.response.issueTokens = false;
        event.response.failAuthentication = true;

        return "hello";
    }

    // Return event for further processing by Cognito
    return event;
};

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-community-response Issue is pending a response from the author or community. labels Dec 12, 2024
@cwomack
Copy link
Member

cwomack commented Dec 12, 2024

@Mustafa-Algoace01, can you confirm if the createAuthChallenge Lambda is being called at all? Or is the error coming from the defineAuthChallenge?

It looks like there are some discrepancies on how the lambdas are set up relative to our docs, but are you just trying to use a custom email sender? If so, this Lambda example may be more relevant.

@cwomack cwomack added pending-community-response Issue is pending a response from the author or community. and removed pending-maintainer-response Issue is pending a response from the Amplify team. labels Dec 12, 2024
@cwomack cwomack self-assigned this Dec 12, 2024
@Mustafa-Algoace01
Copy link
Author

Mustafa-Algoace01 commented Dec 13, 2024

@cwomack i tell you what i want, i want MFA email without using SES so I make custom authentication flow for mfa email first I create define auth lambda function

here its code

export const handler = async (event) => {
try {

console.log('event', JSON.stringify(event, null, 2));
console.log('event.request', JSON.stringify(event.request, null, 2));

const session = event.request.session || [];
const lastChallenge = session[session.length - 1] || {};

if (session.length === 1 && lastChallenge.challengeName === 'SRP_A') {
    event.response.issueTokens = false;
    event.response.failAuthentication = false;
    event.response.challengeName = 'CUSTOM_CHALLENGE';
    
    // // Ensure USER_ID_FOR_SRP is set here
    // event.response.challengeParameters = {
    //     USER_ID_FOR_SRP: event.request.userAttributes.sub,
    // };
} else if (
    session.length > 1 &&
    lastChallenge.challengeName === 'CUSTOM_CHALLENGE' &&
    lastChallenge.challengeResult === true
) {
    event.response.issueTokens = true;
    event.response.failAuthentication = false;
} else {
    event.response.issueTokens = false;
    event.response.failAuthentication = true;
}

console.log('event return', JSON.stringify(event, null, 2));
return event;

} catch (error) {
console.log('error',error)
}
};

here its response

{
"version": "1",
"region": "us-east-2",
"userPoolId": "us-east-2_MEFXCT6FG",
"userName": "b16befbce4a0e69",
"callerContext": {
"awsSdkVersion": "aws-sdk-unknown-unknown",
"clientId": "client id"
},
"triggerSource": "DefineAuthChallenge_Authentication",
"request": {
"userAttributes": {
"sub": "b16b15c0-c051bce4a0e69",
"email_verified": "true",
"cognito:user_status": "CONFIRMED",
"given_name": "Mustafa",
"family_name": "Muhammad",
"email": "[email protected]"
},
"session": [
{
"challengeName": "SRP_A",
"challengeResult": true,
"challengeMetadata": null
}
]
},
"response": {
"challengeName": "CUSTOM_CHALLENGE",
"issueTokens": false,
"failAuthentication": false
}
}

second is "create auth function"

here its code

export const handler = async (event) => {
try {
console.log("Received event:", JSON.stringify(event, null, 2));

    // Check if the challenge is a custom challenge
    if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
        // Generate a 6-digit OTP
        const otp = Math.floor(100000 + Math.random() * 900000).toString();
        const email = event.request.userAttributes.email;
        const username = event.userName;

        // Store OTP as private challenge parameter
        event.response.publicChallengeParameters = {
            USER_ID_FOR_SRP: username, // Provide the user ID for SRP
        };

        event.response.privateChallengeParameters = {
            answer: otp, // Use the generated OTP as the answer
        };

        event.response.challengeMetadata = 'CUSTOM_CHALLENGE';

        console.log('Generated OTP:', otp);  // Be careful not to expose sensitive information in production

        // Send OTP via email
        await sendEmail(email, otp);  // Implement the sendEmail function (e.g., using AWS SES or another service)

        

        console.log("Event return after processing:", JSON.stringify(event, null, 2));
    }

    return event;

} catch (error) {
    console.error("Error processing CreateAuthChallenge:", error);
    throw new Error('Error processing custom challenge');
}

};

and here its response

{
"version": "1",
"region": "us-east-2",
"userPoolId": "us-east-2_MEFXCT6FG",
"userName": "b16b15c0-ce4a0e69",
"callerContext": {
"awsSdkVersion": "aws-sdk-unknown-unknown",
"clientId": "client id"
},
"triggerSource": "CreateAuthChallenge_Authentication",
"request": {
"userAttributes": {
"sub": "b16b15c0-c051-70e69",
"email_verified": "true",
"cognito:user_status": "CONFIRMED",
"given_name": "Mustafa",
"family_name": "Muhammad",
"email": "[email protected]"
},
"challengeName": "CUSTOM_CHALLENGE",
"session": [
{
"challengeName": "SRP_A",
"challengeResult": true,
"challengeMetadata": null
}
]
},
"response": {
"publicChallengeParameters": {
"USER_ID_FOR_SRP": "b13efbce4a0e69"
},
"privateChallengeParameters": {
"answer": "194388"
},
"challengeMetadata": "CUSTOM_CHALLENGE"
}
}

both function work fine

but during signing I use this code for custom auth

const {isSignedIn, nextStep} = await signIn({
username: username,
password: password,
options: {authFlowType: 'CUSTOM_WITH_SRP'},
});

but signing throw this error I cant find any solution of this error
Screenshot 2024-12-13 at 6 45 39 PM

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-community-response Issue is pending a response from the author or community. labels Dec 13, 2024
@pranavosu
Copy link
Member

@Mustafa-Algoace01 I'll go through the code you mentioned, but like @cwomack said using a single custom email sender lambda should be easier to manage if you want to use email MFA without SES

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 13, 2024
@Mustafa-Algoace01
Copy link
Author

@pranavosu Thank you for the suggestion! If I understand correctly, using a custom email sender Lambda for email MFA would indeed simplify management. However, in my use case, Cognito MFA with email is enabled, which requires SES to send verification emails.

My challenge is: how can I implement email MFA without configuring SES? As far as I know, Cognito mandates SES for email delivery when email-based MFA is enabled. Can you clarify how the custom Lambda would bypass this requirement, or if there’s an alternative approach I might be missing?

Screenshot 2024-12-16 at 12 28 15 PM

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 16, 2024
@pranavosu
Copy link
Member

thanks @Mustafa-Algoace01. let me take a closer look at this

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 17, 2024
@jjarvisp
Copy link
Member

jjarvisp commented Dec 17, 2024

Hey @Mustafa-Algoace01, you are correct, SES must be enabled as the email provider to toggle on Email MFA in the console. However, once the custom sender is added, Cognito will send all requests that would have otherwise triggered SES to the lamba function instead.

This also means you'll have to add logic to the custom sender for all your use cases that would otherwise trigger sending an email - sign up, sign in, account recovery, etc, if applicable.

If even just enabling SES (but not using) on your user pool is not an option for you, let us know and we can provide further assistance with the custom auth flow.

@Mustafa-Algoace01
Copy link
Author

Thanks @jjarvisp, this works! And thanks to all of you for helping me

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 19, 2024
@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Auth Related to Auth components/category question General question React Native React Native related issue
Projects
None yet
Development

No branches or pull requests

4 participants