Search code examples
amazon-cognitoamazon-cognito-triggers

How do I continue the CUSTOM_AUTH flow for a CUSTOM_CHALLENGE after PASSWORD_VERIFIER runs?


I have been following this guide to implement email OTP in AWS Cognito. I have my user pool and lambdas configured as the guide suggests, and am able to successfully trigger the SRP_A and PASSWORD_VERIFIER steps. However, I never continue to the CUSTOM_CHALLENGE flow - the PASSWORD_VERIFIER step always provides me with AuthenticationResult that contains the access token, which shows a successful login.

I tried taking away the SRP_A and PASSWORD_VERIFIER steps and just trying the CUSTOM_CHALLENGE, and that works and successfully sends me the OTP to my email. It seems whenever I use PASSWORD_VERIFIER I always get logged in and skip the rest of the CUSTOM_AUTH flow, even if many other CUSTOM_AUTH options are configured. My settings for the define auth lambda are the same as the guide's:

exports.handler = async (event) => {
        if (event.request.session && event.request.session.length === 1
            && event.request.session[0].challengeName === 'SRP_A'
            && event.request.session[0].challengeResult === true) {
            //SRP_A is the first challenge, this will be implemented by cognito. Set next challenge as PASSWORD_VERIFIER.
            event.response.issueTokens = false;
            event.response.failAuthentication = false;
            event.response.challengeName = 'PASSWORD_VERIFIER';
            
        } else if (event.request.session && event.request.session.length === 2
            && event.request.session[1].challengeName === 'PASSWORD_VERIFIER'
            && event.request.session[1].challengeResult === true) {
            //If password verification is successful then set next challenge as CUSTOM_CHALLENGE.
            event.response.issueTokens = false;
            event.response.failAuthentication = false;
            event.response.challengeName = 'CUSTOM_CHALLENGE';
            
        } else if (event.request.session && event.request.session.length >= 5
            && event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE'
            && event.request.session.slice(-1)[0].challengeResult === false) {
            //The user has exhausted 3 attempts to enter correct otp.
            event.response.issueTokens = false;
            event.response.failAuthentication = true;
            
        } else if (event.request.session  && event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE'
            && event.request.session.slice(-1)[0].challengeResult === true) {
            //User entered the correct OTP. Issue tokens.
            event.response.issueTokens = true;
            event.response.failAuthentication = false;
            
        } else {
            //user did not provide a correct answer yet.
            event.response.issueTokens = false;
            event.response.failAuthentication = false;
            event.response.challengeName = 'CUSTOM_CHALLENGE';
        }

        return event;
};

Even if I change the of the issueTokens key to false and failAuthentication to true, if I respond to the PASSWORD_VERIFIER step with the correct user/pass combo I will still log in successfully and it skips any other challenges.

Is there something configured incorrectly for my user pool? I've tried it with two separate user pools - one with MFA optional and one with MFA disabled, but the CUSTOM_AUTH flow still doesn't seem compatible with PASSWORD_VERIFIER.


Solution

  • I figured out the issue here - as you're using the API, the call to respondToAuthChallenge must also be passed Session from the previous response. On a separate challenge I passed Session because I got an error that it was required when I didn't pass it, but for PASSWORD_VERIFIER you don't need Session set, likely because you can use it to create a session itself. Since I was continuing from the SRP_A flow I needed to provide the same session value from that flow.