Question:
How to validate Cognito SMS_MFA code using AWS SDK in NodeJS?

Problem

I have a React login page. When a user enters a username/password, the request goes to the backend and the backend executes the signInUser method as shown below.


NOTE: If MFA is disabled for a user, it returns a set of tokens eg. AccessToken, RefreshToken, and IdToken, I send these tokens to the front end and the user then goes to the dashboard. In short, things work fine.


But if I enable MFA manually incognito for the same user, signInUser will send a challenge called "SMA_MFA" and it sends the following object,


{

    "ChallengeName": "SMS_MFA",

    "Session": "AYABeCMrhuH....",

    "ChallengeParameters": {

        "CODE_DELIVERY_DELIVERY_MEDIUM": "SMS",

        "CODE_DELIVERY_DESTINATION": "+91*******83",      //<<<<<<--------- I receive 6 digit code on my mobile with this response

        "USER_ID_FOR_SRP": "userA"

    }

}



Cognito.Service.js


this.cognitoIdentity = new AWS.CognitoIdentityServiceProvider(this.config);


async signInUser(username, password, cb) {

    

    const params = {

      AuthFlow: "USER_PASSWORD_AUTH",

      ClientId: this.clientId,

      AuthParameters: {

        USERNAME: username,

        PASSWORD: password,

        SECRET_HASH: this.generateHash(username)

      }

    };


    try {

      this.cognitoIdentity.initiateAuth(params, (err, data) => {

        if (err) {

          console.log("signinUser: error", err);

        } 

        else {

          

           if (data?.ChallengeName === "SMS_MFA") {  //<--------- checking if SMA_MFA challenge, sending entire response object back to FE...

                return res.status(200).send(data);

            }


          console.log(data)

        }

      });

    } catch (error) {

      console.log(error);

      return false;

    }

  }


Now, after receiving SMA_MFA challenge object in FE, I redirect the user to a page where he can enter 6 digit code. Then clicking the verify button makes a call to the backend API/verify-mfa-code.


Now in the backend, I don't know how to verify the MFA Code....


What I have tried is as follows,


async verifyMFACode(payload, cb) {

    try {

      const params = {

        Session: payload.Session,           //<<<<<-------- received session (token) that I received from FE (sent by backend to FE) as explained above

        UserCode: payload.userCode          //<<<<<-------- received 6 digit SMS code sent by FE

      }


      

      this.cognitoIdentity.verifySoftwareToken(params, (err, data) => {  //<<<<---- I don't know if I have to user verifySoftwareToken but tried with it.

        if (err) {

          console.log("refreshtoken initiateAuth error:", err)

          cb(err);

        } // an error occurred

        else {

          cb(null, data); // successful response

        }

      });

    } catch (err) {

      cb(err);

    }

  }


I get below error with verifySoftwareToken method,


NotAuthorizedException: Invalid session for the user.


I really have no idea how to answer SMA_Challenge using Nodejs using aws-sdk.


Note: Somewhere people are sending AccessToken in verifySoftwareToken params but I don't get AccessToken because user is yet not signedIn and he has to pass the challenge first.


Solution

The >VerifySoftwareToken API is for >TOTP MFA, not for SMS MFA.


You need to use the >RespondToAuthChallenge API () for validating SMS MFA codes.


For v2 of the JS SDK, here is a minimal example using the >respondToAuthChallenge method:


var AWS = require('aws-sdk');


var client = new AWS.CognitoIdentityServiceProvider();


const userPoolAppClientId = "";

const username = "";

const mfaCode = "";

const session = "";


var request = {

    ChallengeName: 'SMS_MFA', 

    ClientId: userPoolAppClientId,

    ChallengeResponses: {

        'USERNAME': username,

        'SMS_MFA_CODE': mfaCode,

        'SECRET_HASH': this.generateHash(username)

    },

    Session: session

};


client.respondToAuthChallenge(request, function(err, response) {

    if (err) console.log(err, err.stack);

    else {

        var authenticationResult = {

            accessToken: response.AuthenticationResult.AccessToken,

            idToken: response.AuthenticationResult.IdToken,

            refreshToken: response.AuthenticationResult.RefreshToken,

        }


        console.log(authenticationResult);

    }

});



For v3 of the JS SDK, you need to use the >RespondToAuthChallengeCommand JS SDK command.


Here's the same minimal example, compatible with v3:


import {

    CognitoIdentityProviderClient,

    RespondToAuthChallengeCommand

} from "@aws-sdk/client-cognito-identity-provider";


const client = new CognitoIdentityProviderClient(config);


const userPoolAppClientId = "";

const username = "";

const mfaCode = "";

const session = "";


const request = {

    ChallengeName: "SMS_MFA",

    ClientId: userPoolAppClientId,

    ChallengeResponses: {

        "USERNAME": username,

        "SMS_MFA_CODE": mfaCode,

        'SECRET_HASH': this.generateHash(username)

    },

    Session: session,

};


const command = new RespondToAuthChallengeCommand(request);


const response = await client.send(command);


const authenticationResult = {

    accessToken: respondToAuthChallengeResponse.AuthenticationResult.AccessToken,

    idToken: respondToAuthChallengeResponse.AuthenticationResult.IdToken,

    refreshToken: respondToAuthChallengeResponse.AuthenticationResult.RefreshToken,

}


console.log(authenticationResult);


Answered by: >Ermiya Eskandary

Credit: >StackOverflow


Blog Links: 

>How do I customize controller actions in Laravel Jetstream?

>Run Laravel Project to Active Local Server

>How to show encrypted user id in URL in Laravel?

>How to fix Laravel LiveWire listener?


Nisha Patel

Nisha Patel

Submit
0 Answers