Setup cognito hosted UI with MFA

Viewed 241

Cannot find how to setup AWS Cognito MFA for the hosted UI.

Was not able to find any good explanation on how to setup the MFA, most tutorials / example about it skipped over the section.

2 Answers

Created a script after getting the secret hash

My solution Gist

I had to use the client id & client secret to make the secret hash for it all to work.

# AWS Cognito MFA
# MFA is not configured by default when using the AWS Cognito web UI.
# The following script will setup a user account, setup MFA for the user, and return a temporary password.

import boto3, json, pyotp
import string, random
import sys
import hmac, hashlib, base64


class CognitoMFA:
    def __init__(self, USERNAME, EMAIL, TEMP_PASSWORD, CLIENT_ID, CLIENT_SECRET, USER_POOL_ID):
        self.USERNAME = USERNAME
        self.TEMP_PASSWORD = TEMP_PASSWORD
        self.CLIENT_ID = CLIENT_ID
        self.CLIENT_SECRET = CLIENT_SECRET
        self.USER_POOL_ID = USER_POOL_ID
        self.EMAIL = EMAIL
        self.SECRET_HASH = ''
        self.ACCESS_TOKEN = ''
        self.SECRET_TOKEN = ''
        # Create boto3 client for cognito to use
        self.client = boto3.client('cognito-idp')
    
    def __str__(self):
        return self.SECRET_HASH
    
    # Get the mysterious secret hash
    def GetSecretHash(self):
        message = bytes(self.USERNAME+self.CLIENT_ID,'utf-8')
        key = bytes(self.CLIENT_SECRET,'utf-8')
        self.SECRET_HASH = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
        return self.SECRET_HASH

    # Get a password that meets compliance
    def RandomPassword(self):
        chars = string.ascii_uppercase + string.ascii_lowercase + string.digits + string.punctuation
        size = random.randint(16, 20)
        return 'T3m9' + ''.join(random.choice(chars) for x in range(size))

    # Create the user account
    def CreateUser(self):
        response = self.client.admin_create_user(
            UserPoolId = self.USER_POOL_ID,
            Username = self.USERNAME,
            UserAttributes=[
                {
                    'Name': 'email',
                    'Value': self.EMAIL
                },
                {
                    'Name': 'email_verified',
                    'Value': 'true'
                }
            ],
            TemporaryPassword = self.TEMP_PASSWORD,
            MessageAction = 'SUPPRESS',
            DesiredDeliveryMediums = [
                'EMAIL',
            ]
        )
    
    # Change the users password for enabling mfa
    def SetUserPassword(self, permState, random):
        if (random == 'y'):
            password = self.RandomPassword()
        else:
            password = self.TEMP_PASSWORD
        response = self.client.admin_set_user_password(
            UserPoolId = self.USER_POOL_ID,
            Username = self.USERNAME,
            Password = password,
            Permanent = permState
        )
        return password
    
    # Get the user token
    def GetUserToken(self):
        initiateAuth = self.client.initiate_auth(
            AuthFlow = "USER_PASSWORD_AUTH",
            AuthParameters = {
                'USERNAME': self.USERNAME,
                'PASSWORD': self.TEMP_PASSWORD,
                'SECRET_HASH': self.SECRET_HASH
            },
            ClientId = self.CLIENT_ID
        )
        self.ACCESS_TOKEN = initiateAuth["AuthenticationResult"]["AccessToken"]
        return self.ACCESS_TOKEN
    
    # Get the mfa token
    def GetMFAToken(self):
        associateSoftwareToken = self.client.associate_software_token(
            AccessToken = self.ACCESS_TOKEN
        )
        self.SECRET_TOKEN = associateSoftwareToken["SecretCode"]
        return self.SECRET_TOKEN

    # Verify the token
    def VerifyToken(self):
        totp = pyotp.TOTP(self.SECRET_TOKEN)
        response = self.client.verify_software_token(
            AccessToken = self.ACCESS_TOKEN,
            UserCode = totp.now()
        )

    # Enable on mfa for the user account
    def EnableUserMFA(self):
        response = self.client.admin_set_user_mfa_preference(
            SoftwareTokenMfaSettings = {
                'Enabled': True,
                'PreferredMfa': True
            },
            Username = self.USERNAME,
            UserPoolId = self.USER_POOL_ID
        )


if __name__ == '__main__':
    # Check for all of the arguments
    if len(sys.argv) != 6:
        print("[!] Usage python3 CognitoMFA.py USERNAME EMAIL CLIENT_ID CLIENT_SECRET USER_POOL_ID")
        sys.exit(0)

    # Get arguments from the command line
    USERNAME = sys.argv[1]
    EMAIL = sys.argv[2]
    TEMP_PASSWORD = "TempPass123!"
    CLIENT_ID = sys.argv[3]
    CLIENT_SECRET = sys.argv[4]
    USER_POOL_ID = sys.argv[5]

    # Run the appropriate python commands from the class above
    R = CognitoMFA(USERNAME, EMAIL, TEMP_PASSWORD, CLIENT_ID, CLIENT_SECRET, USER_POOL_ID)
    R.GetSecretHash()
    R.CreateUser()
    R.SetUserPassword(True, 'n')
    R.GetUserToken()
    MFA_TOKEM = R.GetMFAToken()
    print(f"Your MFA token: {MFA_TOKEM}")
    R.VerifyToken()
    R.EnableUserMFA()
    Password = R.SetUserPassword(False, 'y')
    print(f"Your password: {Password}")
Other Helpful sources

You can setup MFA in multiple ways with Cognito. The built in MFA lets you use SMS or TOTP. The code below is a proof of concept I done to work with SMS MFA (I had to do this in vanilla JS for various reasons but it would be nicer if you did this with amplify instead). I also done a proof of concept on how to do Email MFA too which will require some more AWS services to setup, that are explained on the repo readme.

Setup your cognito service and configure it with SMS MFA to work with the code below:-

  • Enable SMS as a second factor
  • Create an SMS role Create an App client
  • uncheck 'Generate Client Secret'
  • Add a verified phone number to test with in SNS
  • Optional - you can add a signup trigger to auto-verify.confirm users at signup - check the github link for the autoverify file
</style>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
    
    <script src="aws-cognito-sdk.min.js"></script>
    <script src="amazon-cognito-identity.min.js"></script>
    
</head>
<body >
<br>


<span style="text-align:center;"><h3>Login</h3></span>
<form id="signIn">
<ul>
    <li><label>Username <span>*</span></label><input type="text" name="username"  placeholder="UID" /></li>
    <li><label>Password <span>*</span></label><input type="password" name="password"  placeholder="Password" /></li>    
    <li>
        <input type="submit" value="Submit" />
    </li>
</ul>
</form>

<div id="resultsSignIn" ></div>


<span style="text-align:center;"><h3>Sign Up</h3></span>
<form id="signUp">
<ul>
    <li><label>Username <span>*</span></label><input type="text" name="usernameSignUp" placeholder="UID" /></li>
    <li><label>Password <span>*</span></label><input type="password" name="passwordSignUp" placeholder="Password" /></li>  
    <li><label>Email <span>*</span></label><input type="email" name="email" placeholder="john@example.com" /></li>  
    <li><label>Phone number <span>*</span></label><input type="tel" name="telephone" placeholder="+4412345678" /></li>      
    <li>
        <input type="submit" value="Submit" />
    </li>
</ul>
</form>

<div id="resultsSignUp"></div>
</body>
</html>
<script type="text/javascript">
//var dataResult;
$(document).ready(function() {

    $('#signUp').submit(function(event) {
        
        var poolData = {
            UserPoolId : '', // your user pool id here
            ClientId : '' // your app client id here
        };
        var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

        var attributeList = [];

        var dataEmail = {
            Name: 'email',
            Value: $('input[name=email]').val(),
        };

        var dataPhoneNumber = {
            Name: 'phone_number',
            Value: $('input[name=telephone]').val(),
        };
        
        var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
        var attributePhoneNumber = new AmazonCognitoIdentity.CognitoUserAttribute(dataPhoneNumber);

        attributeList.push(attributeEmail);
        attributeList.push(attributePhoneNumber);
        
        console.log(attributeList)

        userPool.signUp($('input[name=usernameSignUp]').val(), $('input[name=passwordSignUp]').val(), attributeList, null, function(err, result) {
            if (err) {
                alert(err.message || JSON.stringify(err));
                return;
            }
            var resultStr = 'Sign Up Successful';
            console.log(resultStr);
            $('#resultsSignUp').html(resultStr);
            var cognitoUser = result.user;
            console.log('user name is ' + cognitoUser.getUsername());
        });
    });


    $('#signIn').submit(function(event) {
        //-------------------user pool
        AWSCognito.config.region = '';
     
        var poolData = {
            UserPoolId : '', // your user pool id here
            ClientId : '' // your app client id here
        };
        var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
        
        //------------------Authentication-------------------------
        var userData = {
            Username : $('input[name=username]').val(), // your username here
            Pool : userPool
        };
        var authenticationData = {
            Username : $('input[name=username]').val(), // your username here
            Password : $('input[name=password]').val(), // your password here
        };
        var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);

        var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
        cognitoUser.authenticateUser(authenticationDetails, {
            onSuccess: function (result) {
                console.log('success');
                var resultStr = 'Login Successful';
                console.log(resultStr);
                $('#resultsSignIn').html(resultStr);
                
                cognitoUser.getUserAttributes(function(err, result) {
                if (err) {
                    alert(err.message || JSON.stringify(err));
                    return;
                }
                for (i = 0; i < result.length; i++) {
                    console.log(
                        'attribute ' + result[i].getName() + ' has value ' + result[i].getValue()
                    );
                }
            });
            },

            onFailure: function(err) {
                alert(err);
            },
            mfaRequired: function(codeDeliveryDetails) {
                var verificationCode = prompt('Please input verification code' ,'');
                cognitoUser.sendMFACode(verificationCode, this);
            },
            newPasswordRequired: function(userAttributes, requiredAttributes) {
            // User was signed up by an admin and must provide new
            // password and required attributes, if any, to complete
            // authentication.

            // userAttributes: object, which is the user's current profile. It will list all attributes that are associated with the user.
            // Required attributes according to schema, which don’t have any values yet, will have blank values.
            // requiredAttributes: list of attributes that must be set by the user along with new password to complete the sign-in.


            // Get these details and call
            // newPassword: password that user has given
            // attributesData: object with key as attribute name and value that the user has given.
            delete userAttributes.email_verified;
            delete userAttributes.phone_number;
            if(userAttributes.phone_number_verified){
                delete userAttributes.phone_number_verified;
            }
            var newPassword = prompt('Please input your new password' ,'');
            cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this)
            return;
            }
        });
        return false;
    });
});
</script>

Related