Reputation: 61
I have a React web application (utilizing aws-amplify) which is connecting to AWS Cognito User Pool for authentication.
User of my application can optionally enable SMS MFA from settings.
I tried to enable SMS MFA using aws amplify npm package but I'm facing an error saying
{
"__type": "InvalidParameterException",
"message": "User does not have delivery config set to turn on SMS_MFA"
}
I have set MFA to "Optional" on AWS Cognito User Pools settings as seen in the screenshot below.
And here's my component logic
import React, { useState, useEffect } from 'react';
import { Card, Grid, Typography, Box, Switch } from '@material-ui/core';
import { Auth } from 'aws-amplify';
const Profile = () => {
const [currentUser, setCurrentUser] = useState(null);
// use this state to highlight MFA status
const [isMFAEnabled, setIsMFAEnabled] = useState(false);
const toggleMFA = async () => {
const preferredMFA = isMFAEnabled ? 'NOMFA' : 'SMS';
try {
const result = await Auth.setPreferredMFA(currentUser, preferredMFA);
setIsMFAEnabled(!isMFAEnabled);
// Auth.enableSMS(currentUser);
} catch (error) {
console.log('error :: ', error);
}
};
useEffect(() => {
async function fetchProfileData() {
const user = await Auth.currentAuthenticatedUser();
setCurrentUser(user);
// enable or disabled MFA switch
const { preferredMFA } = user;
setIsMFAEnabled(!(preferredMFA && preferredMFA === 'NOMFA'));
}
fetchProfileData();
}, []);
return (
<>
<Grid
container
direction="row"
justifyContent="center"
alignItems="center"
>
<Grid item xs={12} sm={12} md={10} lg={8} xl={6}>
<Card>
<Typography variant="h4">Security</Typography>
<Box
display="flex"
flexDirection="row"
alignItems="center"
>
<Typography variant="subtitle">
Two factor authentication
</Typography>
<Switch
checked={isMFAEnabled}
onChange={toggleMFA}
name="MFA"
/>
</Box>
</Card>
</Grid>
</Grid>
</>
);
};
export default Profile;
Upvotes: 5
Views: 8903
Reputation: 5133
If you want to setup the TOTP for user you have to call the AWS Cognito APIs in the following order
The associate software token will give you an SecretCode
which you will convert to a QR either so that user can scan it with an authenticator app. Then you will call the verify software token and pass it the code generated by the authenticator app. And finally you will enable the MFA by calling the set user preference API. And voila.
Upvotes: 4
Reputation: 341
I got this error too and it took me a while to figure out what was wrong
My Cognito's configuration:
No Verification
This is how I solved the issue:
First, I used AWS CLI to reproduce the steps to onboard users:
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>
This is why I create users with --desired-delivery-mediums "EMAIL"
, above
FORCE_CHANGE_PASSWORD
statusaws cognito-idp admin-set-user-password --user-pool-id <user-pool-id> --username <user_email> --password "NewSecret@111" --permanent --profile <aws_config_profile>
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>
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:
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);
}
};
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.
Upvotes: 5