4

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.

enter image description here

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;
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Sagar Shah
  • 51
  • 1
  • 6

3 Answers3

4

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:

  1. 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>
  1. 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

  1. The frontend would route the user to the correct UI
  2. 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>
  1. 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>
  1. 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:

  1. 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);
    }
  };
  1. 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.

raulra08
  • 191
  • 2
  • 5
  • Trying to use TOTP MFA and I'm running into the "User does not have delivery config set to turn on SOFTWARE_TOKEN_MFA" error. When I follow these steps, except adding a phone number (bc no SMS), it unfortunately doesn't solve the problem. – Nick K9 Feb 12 '22 at 23:40
  • @NickK9 You seem to be trying the TOTP_MFA flow but above I have explained the SMS_MFA. You will need to adapt the `aws cognito-idp` commands for TOTP_MFA and also expect the TOTP_MFA challenge from amplify's signIn response. – raulra08 Mar 04 '22 at 14:37
  • Thanks for the reply! In the end, I [adopted Amplify UI](https://stackoverflow.com/a/71142110/1749551). TOTP support is built in, and works well. – Nick K9 Mar 04 '22 at 14:56
  • 1
    I am using AdminSetUserMFAPreferenceCommand to set SoftwareTokenMfaSettings and its giving me the same error "InvalidParameterException: User does not have delivery config set to turn on SOFTWARE_TOKEN_MFA". Not sure what is delivery config – Samay Apr 20 '23 at 21:17
2

If you want to setup the TOTP for user you have to call the AWS Cognito APIs in the following order

  1. Associate Software Token
  2. Verify Software Token
  3. Set User MFA Preference

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.

Hadi Mir
  • 4,497
  • 2
  • 29
  • 31
0

You will see this MFA issue if you don't have phone_number ready.

fanartie
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 12 '22 at 23:37