Search code examples
javascriptamazon-web-servicesamazon-cognitomulti-factor-authenticationtotp

How to setup AWS Cognito TOTP MFA?


I am trying to setup MFA authentication using AWS Cognito as a small proof of concept for a work project. I have managed to get username & password with a MFA code sent via SMS working fine.

Struggling to get the TOTP method which is shown in use case 27 working with my small login app - https://www.npmjs.com/package/amazon-cognito-identity-js

i have modified the associateSecretCode so that it should show me a the secret code to then enter into my authenticator app but this doesnt display when i attempt to login with a valid user.

What am i doing wrong?

Heres my code:

<body>
<form>

<ul class="form-style-1">
    <li><label>UserID <span class="required">*</span></label><input type="text" name="username" class="field-divided" placeholder="UID" /></li>
    <li><label>Password <span class="required">*</span></label><input type="password" name="password" class="field-divided" placeholder="Password" /></li>    
    <li>
        <input type="submit" value="Submit" />
    </li>
</ul>
</form>
<div id="results" class="form-style-1"></div>
</body>
</html>
<script type="text/javascript">
//var dataResult;
$(document).ready(function() {

    $('form').submit(function(event) {
    
        
        //-------------------user pool
        AWSCognito.config.region = 'eu-west-2';
     
        var poolData = {
            UserPoolId : 'user pool id here', 
            ClientId : '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('OnSuccess')
                var accessToken = result.getAccessToken().getJwtToken();
                cognitoUser.associateSoftwareToken(this);
            },

            onFailure: function(err) {
                console.log('onFailure')
                alert(err.message || JSON.stringify(err));
            },

            mfaSetup: function(challengeName, challengeParameters) {
                console.log('mfaSetup')
                cognitoUser.associateSoftwareToken(this);
            },

            associateSecretCode: async secretCode => {
                console.log("SECRET CODE: ", secretCode);
                $('#results').html(secretCode);
                
                setTimeout(() => {
                  const challengeAnswer = prompt("Please input the TOTP code.", "");
                  cognitoUser.verifySoftwareToken(challengeAnswer, "My TOTP device", {
                    onSuccess: session => console.log("SUCCESS TOTP: ", session),
                    onFailure: err => console.error("ERROR TOTP: ", err)
                  });
                }, 2000);
            },

            selectMFAType: function(challengeName, challengeParameters) {
                console.log('selectMFAType')
                var mfaType = prompt('Please select the MFA method.', ''); // valid values for mfaType is "SMS_MFA", "SOFTWARE_TOKEN_MFA"
                cognitoUser.sendMFASelectionAnswer(mfaType, this);
            },

            totpRequired: function(secretCode) {
                console.log('totpRequired')
                var challengeAnswer = prompt('Please input the TOTP code.', '');
                cognitoUser.sendMFACode(challengeAnswer, this, 'SOFTWARE_TOKEN_MFA');
            },

            mfaRequired: function(codeDeliveryDetails) {
                console.log('mfaRequired')
                var verificationCode = prompt('Please input verification code', '');
                cognitoUser.sendMFACode(verificationCode, this);
            }
        });
    });
});
</script>

Solution

  • The best solution I've found is to use Amplify UI components. You don't need to take all the rest of Amplify, you can just grab the two relevant JS libraries, import, configure, and then wrap the page you need in the withAuthenticator HOC. The default setup handles both registration and challenge with sofware and SMS TOTP, as well as forgot password and create account flows. All the error handling you'd expect is included, and even language/theme customization and localization. It's very comprehensive.

    While it was painful to figure out, the steps are actually not at all complicated, assuming you already have a Cognito User Pool set up. (Note: Amplify requires a User Pool client which does not use a client secret.)

    You can find sample code in my answer to a similar StackOverflow question.