How to include TOTP MFA in AWS Cognito authentication process
Asked Answered
B

1

7

I'm using Cognito user pools to authenticate my web application. I've got it all working right now but now I need to enable MFA for it. This is how I do it right now (all the code provided are server-side code):

  1. Signing up the user:
const cognito = new AWS.CognitoIdentityServiceProvider();
cognito.signUp({
    ClientId,
    Username: email,
    Password,
}).promise();
  1. An email is sent to the user's address (mentioned as username in the previous function call) with a code inside.

  2. The user reads the code and provides the code to the next function call:

cognito.confirmSignUp({
    ClientId,
    ConfirmationCode,
    Username: email,
    ForceAliasCreation: false,
}).promise();
  1. The user logs in:
const tokens = await cognito.adminInitiateAuth({
    AuthFlow: 'ADMIN_NO_SRP_AUTH',
    ClientId,
    UserPoolId,
    AuthParameters: {
        'USERNAME': email,
        'PASSWORD': password,
    },
}).promise();

I'm pretty happy with this process. But now I need to add the TOTP MFA functionality to this. Can someone tell me how these steps will be changed if I want to do so? BTW, I know that TOTP MFA needs to be enabled for the user pool while creating it. I'm just asking about how it affects my sign-up/log-in process.

Bluecoat answered 15/1, 2020 at 17:30 Comment(0)
B
11

Alright, I found a way to do this myself. I must say, I couldn't find any documentation on this so, use it at your own risk!

Of course, this process assumes you have a user pool with MFA enabled (I used the TOTP MFA).

  1. Signing up the user:
const cognito = new AWS.CognitoIdentityServiceProvider();
cognito.signUp({
    ClientId,
    Username: email,
    Password,
}).promise();
  1. An email is sent to the user's address (mentioned as username in the previous function call) with a code inside.

  2. The user reads the code and provides the code to the next function call:

cognito.confirmSignUp({
    ClientId,
    ConfirmationCode: code,
    Username: email,
    ForceAliasCreation: false,
}).promise();
  1. The first log in:
await cognito.adminInitiateAuth({
    AuthFlow: 'ADMIN_NO_SRP_AUTH',
    ClientId,
    UserPoolId,
    AuthParameters: {
        'USERNAME': email,
        'PASSWORD': password,
    },
}).promise();

At this point, the return value will be different (compared to what you'll get if the MFA is not enforced). The return value will be something like:

{
  "ChallengeName": "MFA_SETUP",
  "Session": "...",
  "ChallengeParameters": {
    "MFAS_CAN_SETUP": "[\"SOFTWARE_TOKEN_MFA\"]",
    "USER_ID_FOR_SRP": "..."
  }
}

The returned object is saying that the user needs to follow the MFA_SETUP challenge before they can log in (this happens once per user registration).

  1. Enable the TOTP MFA for the user:
cognito.associateSoftwareToken({
  Session,
}).promise();

The previous call is needed because there are two options and by issuing the given call, you are telling Cognito that you want your user to enable TOTP MFA (instead of SMS MFA). The Session input is the one return by the previous function call. Now, this time it will return this value:

{
  "SecretCode": "...",
  "Session": "..."
}
  1. The user must take the given SecretCode and enter it into an app like "Google Authenticator". Once added, the app will start showing a 6 digit number which is refreshed every minute.

  2. Verify the authenticator app:

cognito.verifySoftwareToken({
  UserCode: '123456',
  Session,
}).promise()

The Session input will be the string returned in step 5 and UserCode is the 6 digits shown on the authenticator app at the moment. If this is done successfully, you'll get this return value:

{
  "Status": "SUCCESS",
  "Session": "..."
}

I didn't find any use for the session returned by this object. Now, the sign-up process is completed and the user can log in.

  1. The actual log in (which happens every time the users want to authenticate themselves):
await cognito.adminInitiateAuth({
    AuthFlow: 'ADMIN_NO_SRP_AUTH',
    ClientId,
    UserPoolId,
    AuthParameters: {
        'USERNAME': email,
        'PASSWORD': password,
    },
}).promise();

Of course, this was identical to step 4. But its returned value is different:

{
  "ChallengeName": "SOFTWARE_TOKEN_MFA",
  "Session": "...",
  "ChallengeParameters": {
    "USER_ID_FOR_SRP": "..."
  }
}

This is telling you that in order to complete the login process, you need to follow the SOFTWARE_TOKEN_MFA challenge process.

  1. Complete the login process by providing the MFA:
cognito.adminRespondToAuthChallenge({
  ChallengeName: "SOFTWARE_TOKEN_MFA",
  ClientId,
  UserPoolId,
  ChallengeResponses: {
    "USERNAME": config.username,
    "SOFTWARE_TOKEN_MFA_CODE": mfa,
  },
  Session,
}).promise()

The Session input is the one returned by step 8 and mfa is the 6 digits that need be read from the authenticator app. Once you call the function, it will return the tokens:

{
  "ChallengeParameters": {},
  "AuthenticationResult": {
    "AccessToken": "...",
    "ExpiresIn": 3600,
    "TokenType": "Bearer",
    "RefreshToken": "...",
    "IdToken": "..."
  }
}
Bluecoat answered 16/1, 2020 at 0:42 Comment(3)
Thanks Mehran for sharing all of that. The Cognito team has recently updated some of our API docs to explain this better. One thing I can add to the above is that the session returned from VerifySoftwareToken in step 7 above can be used directly with an AdminRespondToAuthChallenge request so you don't have to start over with signing in. Include ChallengeName: "MFA_SETUP", the USERNAME in ChallengeResponses, and the Session from VerifySoftwareTokenSextain
Thanks for this great write up. It was really clear and in proper sequence. Your answer helped me a lot.Abilene
For future readers, You must use verifySoftwareToken response { "Status": "SUCCESS", "Session": "..."} immediately in respondToAuthChallenge/adminRespondToAuthChallenge` api call to get tokens as follow respondToAuthChallenge ({ ChallengeName: MFA_SETUP", Session: verifySoftwareToken.Session, ChallengeResponses: { "USERNAME": "your user name"}}). This way you don't have to login again and user will be able login directly right after doing MFA setup.Abilene

© 2022 - 2024 — McMap. All rights reserved.