Skip to content

AWS Cognito Custom Auth Flow Returning “Incorrect username or password” for Passwordless Login

0

I’m implementing a custom passwordless authentication flow using AWS Cognito for my application. The idea is to allow users to log in with a phone number and an OTP sent via WhatsApp using Meta’s API.

I have implemented the following Lambda triggers for the custom authentication flow:

  1. DefineAuthChallenge: Determines the next challenge and sets it as CUSTOM_CHALLENGE.
  2. CreateAuthChallenge: Generates the OTP, stores it temporarily, and sends it to the user via WhatsApp.
  3. VerifyAuthChallengeResponse: Validates the OTP provided by the user.

The flow works as expected until the VerifyAuthChallengeResponse step. After verification, Cognito throws the following error:

NotAuthorizedException: Incorrect username or password.

What I’ve Checked:

  • The VerifyAuthChallengeResponse Lambda successfully validates the OTP and returns true for event.response.answerCorrect.
  • The phone number (username) is correctly formatted in E.164 format.
  • The Cognito user has the CONFIRMED status and required attributes (phone_number, phone_number_verified).
  • No typos in the event response properties.

What I Need Help With:

  1. Why is Cognito throwing the “Incorrect username or password” error even after the challenge is successfully verified?
  2. Are there specific fields I need to set in the Lambda trigger responses to ensure Cognito recognizes the authentication as successful?
  3. Is it possible that this issue arises because I’m testing with an existing user who was created before implementing the custom authentication flow?

Here’s an outline of my VerifyAuthChallengeResponse Lambda:

exports.handler = async (event) => {
    const userAnswer = event.request.challengeAnswer;
    const correctAnswer = event.request.privateChallengeParameters.otp;

    if (userAnswer === correctAnswer) {
        event.response.answerCorrect = true;
    } else {
        event.response.answerCorrect = false;
    }

    return event;
};

the nestJs code:

async initiateAuth(phoneNumber: string): Promise<any> {
    const command = new InitiateAuthCommand({
      ClientId: this.clientId,
      AuthFlow: "CUSTOM_AUTH",
      AuthParameters: {
        USERNAME: phoneNumber,
        password: "test@1234"
      },
    });

    try {
      const response = await this.client.send(command);
      return response;
    } catch (error: any) {
      this.logger.debug(
        `Error authenticating user, error: ${JSON.stringify(error)}`,
      );
      throw error;
    }
  }

  async respondToAuthChallenge(
    username: string,
    phoneNumber: string,
    otp: string,
    session: string,
  ): Promise<any> {
    const command = new RespondToAuthChallengeCommand({
      ClientId: this.clientId,
      ChallengeName: "CUSTOM_CHALLENGE",
      Session: session,
      ChallengeResponses: {
        USERNAME: username,
        ANSWER: otp,
      },
    });

    try {
      const response = await this.client.send(command);
      return response;
    } catch (error: any) {
      this.logger.debug(
        `Error responding to challenge: ${JSON.stringify(error)}`,
      );
      throw error;
    }
  }

Any insights or troubleshooting tips would be highly appreciated! Let me know if more details or logs are needed.

asked 2 years ago892 views

2 Answers
1
Accepted Answer

Problem:

I encountered an issue with AWS Cognito’s DefineAuthChallenge Lambda function. The function wasn’t handling the scenario where the authentication challenge (e.g., OTP validation) was successfully completed. This resulted in a NotAuthorizedException: Incorrect username or password error.

Solution:

The problem was that the DefineAuthChallenge Lambda function didn’t correctly issue tokens when the challenge was successfully answered. To fix this, I added logic to handle the case where the challenge result was true.

Here is the updated code snippet:

exports.handler = async (event) => {
    console.log('Define Auth Challenge event:', JSON.stringify(event, null, 2));

    if (event.triggerSource === "DefineAuthChallenge_Authentication") {
        // Check if this is the first challenge or the previous challenge was successfully answered
        if (event.request.session.length === 0 ||
            (event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE' &&
                event.request.session.slice(-1)[0].answer)) {
            console.log("Init authentication...");
            event.response.issueTokens = false; // Do not issue tokens yet
            event.response.failAuthentication = false; // Continue to the next challenge
            event.response.challengeName = 'CUSTOM_CHALLENGE'; // Create a custom challenge
        } 
        // Handle the case where the challenge is successfully answered
        else if (event.request.session.length > 0 && event.request.session[0].challengeResult === true) {
            console.log("Authentication is done...");
            event.response.issueTokens = true; // Issue tokens
            event.response.failAuthentication = false;
        } 
        // Handle failure case
        else {
            console.log("Failed to authenticate...");
            event.response.issueTokens = false;
            event.response.failAuthentication = true;
        }
    }

    return event;
};

Explanation:

  • The added block checks if the most recent session’s challengeResult is true.
  • If so, it sets event.response.issueTokens to true, allowing Cognito to issue tokens for the authenticated user.
  • Without this logic, Cognito will not issue tokens even if the challenge is answered correctly, resulting in the NotAuthorizedException.

Note: Ensure that the other Lambda triggers (CreateAuthChallenge and VerifyAuthChallengeResponse) are also implemented correctly to handle the OTP generation and validation process.

answered 2 years ago

EXPERT

reviewed 2 years ago

1

Based on the information provided, there are a few potential reasons for the "Incorrect username or password" error you're encountering in your custom authentication flow:

  1. The Define Auth Challenge Lambda function may not be correctly signaling the end of the authentication process. After the VerifyAuthChallengeResponse Lambda successfully validates the OTP, the Define Auth Challenge Lambda should indicate that the authentication is complete and tokens should be issued.

  2. The authentication flow might not be completing all necessary steps. Custom authentication in Cognito often requires multiple challenge-response cycles, even if only one challenge is actually presented to the user.

  3. The password field in your InitiateAuth call might be causing issues. For a truly passwordless flow, you shouldn't need to include a password parameter.

To address these issues:

  1. Ensure your Define Auth Challenge Lambda is properly configured to end the authentication process. After the OTP is verified, it should return a response like this:
{
  challengeName: 'CUSTOM_CHALLENGE',
  issueTokens: true,
  failAuthentication: false
}
  1. Make sure your client-side code is prepared to handle multiple challenge-response cycles. Even if you only have one actual challenge, you may need to respond to multiple CUSTOM_CHALLENGE responses from Cognito before receiving tokens.

  2. In your InitiateAuth call, try removing the password parameter entirely. For a passwordless flow, you typically only need to provide the USERNAME.

  3. Double-check that the user's phone number is correctly formatted and matches exactly what's stored in Cognito.

  4. Verify that your Create Auth Challenge Lambda is correctly setting both the publicChallengeParameters and privateChallengeParameters.

  5. In your Verify Auth Challenge Response Lambda, make sure you're returning the entire event object, not just the response part.

If these steps don't resolve the issue, you may want to add more detailed logging in your Lambda functions to track the exact flow of the authentication process. This can help identify at which point the process is failing.

Regarding your question about testing with an existing user: While it's possible that this could cause issues, it shouldn't if the user's attributes are correctly set. However, for testing purposes, you might want to try creating a new user specifically for this authentication flow to rule out any potential conflicts with existing user data.

Remember, custom authentication flows in Cognito can be complex, and often require careful coordination between multiple Lambda functions and your client application. Detailed logging and step-by-step debugging can be invaluable in troubleshooting these types of issues.

Sources
Custom authentication challenge Lambda triggers - Amazon Cognito
Verify Auth challenge response Lambda trigger - Amazon Cognito
Create Auth challenge Lambda trigger - Amazon Cognito

answered 2 years ago

EXPERT

reviewed 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.