I got this error too and it took me a while to figure out what was wrong
My Cognito's configuration:
- Using custom messages trigger lambda, users are invited to sign-up after they receive an email
- TOTP is enabled (let's ignore this for now)
- SMS is enabled
- Attribute verification is set to
No Verification
This is how I solved the issue:
First, I used AWS CLI to reproduce the steps to onboard users:
- Admin creates a new user
aws cognito-idp admin-create-user \
--user-pool-id <user-pool-id> \
--username <user_email> \
--user-attributes Name="email",Value="<user_email>" Name="name",Value="Alice" Name="family_name",Value="Doe" Name="email_verified",Value="true" \
--force-alias-creation \
--temporary-password "LongSecret132@" \
--desired-delivery-mediums "EMAIL" \
--profile <aws_config_profile>
- A CustomMessage_AdminCreateUser is triggered synchronouly, if lambda succeeds the user receives an email with a link to complete sing-up.
This is why I create users with --desired-delivery-mediums "EMAIL"
, above
- The frontend would route the user to the correct UI
- The user has to set a password, otherwise the user would remain in
FORCE_CHANGE_PASSWORD
status
aws cognito-idp admin-set-user-password --user-pool-id <user-pool-id> --username <user_email> --password "NewSecret@111" --permanent --profile <aws_config_profile>
- The user has to set a phone number. Done now because they aren't asked before step 1. If you want you could ask before and set the
Name="phone_number",Value="<user_phone_number>"
and Name="phone_number_verified",Value="true"
in step 1.
aws cognito-idp admin-update-user-attributes \
--user-pool-id <user-pool-id> \
--username <user_email> \
--user-attributes Name="phone_number",Value="+447000200100" Name="phone_number_verified",Value="true" \
--profile <aws_config_profile>
- Sequencially, the user mfa prefence needs to be set before users can authenticate themselves.
aws cognito-idp admin-set-user-mfa-preference \
--user-pool-id <user-pool-id> \
--username <user_email> \
--sms-mfa-settings Enabled=true,PreferredMfa=true \
--profile <aws_config_profile>
So I did this all manually and then using the UI I tried to authenticate and Cognito did send me an SMS! And no error.
My conclusion is that the error happens when the user attributes (step 5) and mfa preference (step 6) arent setup properly and in the correct order.
You can try the commands above with a test user.
Second, Amplify docs were useful. The React code could do something like:
- Cosinder:
- the api created the user (steps 1).
- the user set their new password (steps 2 to 4)
- the api registered the user's phone number (step 5)
All you are left with is handling the MFA either for the first time or after,
const fetchCognitoUser = async (flag = false) => {
const user = await Auth.currentAuthenticatedUser({
bypassCache: flag,
});
return user;
};
const handleMfaAuth = async ({ userObject, mfaToken }) => {
try {
// when MFA is being setup the first time
// checks if this does not exist because
// setting mfa to optional in cognito will not give you a challengeName
if (!userObject.challengeName) {
await Auth.verifyTotpToken(userObject, mfaToken);
// This would be step 6 in the CLI version above.
await Auth.setPreferredMFA(userObject, 'SMS');
const cognitoUser = await fetchCognitoUser().catch((err) => {
console.error(err);
});
if (cognitoUser) {
// any logic to ensure correct Authentication
// Load apps landing page.
}
}
// when the user enters the MFA token
else if (userObject.challengeName === 'SMS_MFA') {
await Auth.confirmSignIn(userObject, mfaToken, 'SMS_MFA');
const cognitoUser = await fetchCognitoUser().catch((err) => {
console.error(err);
});
if (cognitoUser) {
// any logic to ensure correct Authentication
// Load apps landing page.
}
}
} catch (error) {
// Token is not verified
setShowError(true);
}
};
- Say the user credentials are set but their MFA got deactivated (e.g manually in AWS Console) or they are simply performing log-in again.
const handleSignIn = async ({ username, password }) => {
try{
const user = await Auth.signIn(username, password);
// if MFA hasn't been setup
// checks if this does not exist because
// setting mfa to optional in cognito will not give you a challengeName
if (!user.challengeName) {
// show the page to set up MFA
setShowSetupMfa(true);
} else if (user.challengeName === 'SMS_MFA') {
// If MFA is enabled, sign-in should be confirmed with the confirmation code
const loggedUser = await Auth.confirmSignIn(
user, // Return object from Auth.signIn()
code, // Confirmation code captured in the UI
mfaType // MFA Type e.g. SMS_MFA, SOFTWARE_TOKEN_MFA
);
// show the page to enter mfa token
setShowMfa(true);
}
} catch (error) {
// handle error
}
}
Hope this helps
Make sure Cognito is correctly configured. You need SMS enabled, the documentation explains how to do it
I must say that boto3's documentation helped me understand my options and how some things work in cognito, I definetly recommend taking a look for those of you having similar or slightly different issues.