I'm attempting to implement OTP authentication using AWS Cognito. Here's the flow of the authentication:
I followed the AWS documentation and configured three AWS Cognito triggers: defineAuthChallenge
, createAuthChallenge
, verifyAuthChallengeResponse
.
Everything works okay. However, if the user enters the verification code incorrectly for the first time and then sends the correct code, the verifyAuthChallengeResponse
seems to "cache" the incorrect code result and constantly fails. I also tried to follow the community guidelines, which suggest returning the following response from the defineAuthChallenge
upon a failed attempt:
async function defineAuthChallengeLambda(event) {
if (event.request.session.length === 0) {
// [CASE-1] If no session, it means the user is authenticating for the first time
event.response.failAuthentication = false;
event.response.issueTokens = false;
event.response.challengeName = "CUSTOM_CHALLENGE";
} else if (event.request.session.length === 1) {
if (event.request.session[0].challengeResult === true) {
// [CASE-2] If the user answered correctly -> authenticated
event.response.failAuthentication = false;
event.response.issueTokens = true;
} else {
// [CASE-3] If the user didn't answer correctly -> repeat the flow again (!!!)
event.response.challengeName = "CUSTOM_CHALLENGE";
event.response.failAuthentication = false;
event.response.issueTokens = false;
}
}
return event;
}
The [CASE-3] is proposed by the community, but whenever this case happens, my command which triggers the second attempt of the authentication fails with an error (see code below):
import { RespondToAuthChallengeCommand } from "@aws-sdk/client-cognito-identity-provider"
const command = new RespondToAuthChallengeCommand({
ClientId: envs.IL_USER_POOL_CLIENT_ID,
ChallengeName: "CUSTOM_CHALLENGE",
ChallengeResponses: {
USERNAME: bag.phoneNumber,
ANSWER: bag.code
},
Session: bag.session,
})
await new CognitoIdentityProvider().send(command) // Error: Authentication result expected
It seems that the second try expects either event.response.failAuthentication = true
or event.response.issueTokens = true
, but neither of those cases is valid for the second retry.
Do you know what I'm doing wrong?"
I must confess that I made a mistake in my logic, and the errors that were appearing were due to a custom error I had defined. However, I also found a solution for it.
Whenever I call RespondToAuthChallengeCommand
, the result of this command includes a session property in case code verification fails. Therefore, I had to take this returned string and pass it again to the next request. After doing this, the flow started to work as expected. The defineAuthChallenge
trigger began to receive more than one session, including failed verifications, allowing me to implement multiple retries.
In other words, in my previous implementation, I used the Session property for all my code attempts, which was incorrect. Now, I retrieve a new session string each time from the RespondToAuthChallengeCommand
whenever it fails and pass it to the next attempt (request).