Get #Amazon #Prime for this #holiday #amazonprime #christmas #2019

How to migrate your user base to #aws #cognito? #AwsAmplify

AWS Cognito User Migration

Thinking of switching over to user aws cognito to manage your user pool and authenticate your users, there are multiple ways to migrate your users to aws cognito. In my opinion, user migration should occur in a way that introduces the least effort from the users. Check out these 3 ways:
i) upload your users using a csv file
ii) use admin_create_user API

iii) write a user migration lambda trigger
There could be more ways but I am only aware of these 3 at the moment. 

Method 1: Upload using a csv file

1) Download a header file
2) Write a script to query your database and prep the user csv file

3) Upload the csv file on aws cognito console
There is a header file for all the fields that you optionally or mandatorily have to fill out. 
To get this header file: aws console > cognito > users and groups (left hand side under general settings) > on Users tab > click Import Users > Download CSV header

This is also where you upload your user csv file if you upload it using aws cognito console. You can check out this aws doc if you prefer to upload using aws cli.

Check out an example of header (taken from aws doc): 


cognito:username,name,given_name,family_name,middle_name,nickname,preferred_username,profile,picture,website,email,email_verified,gender,birthdate,zoneinfo,locale,phone_number,phone_number_verified,address,updated_at,cognito:mfa_enabled

The headers could be different depending on custom fields that you have added.

Note:
i) Remember that the uploaded users will have a status of RESET_REQUIRED, which means, the users have to reset their password in order to log in successfully at the first time. So, it would require a redirection flow on the login process.

ii) This actually has a log on CloudWatch. You can use it to check the error messages in case your user import job isn't executed successfully.
To view the log on CloudWatch, follow the following steps:
CloudWatch > Logs on left side panel > filter for /aws/cognito/ > click on the user group > look for your import job name

Method 1 Troubleshooting

If you have been wondering why your csv upload to cognito user has been failed even though you follow this AWS Creating the User Import .csv File document closely. You could be facing the same problems that I had.

Problems that I faced:
i) I didn't know both email_verified or phone_number_verified are required to be filled. On the document, it says OR. Thus, I only filled out email_verified as I use emails for login. Remember to fill out both fields.

ii) This seems to be a UI problem of cognito console. The IAM name drop-down list seems to be cut off after 50 or so. In case your role name is ordered alphabetically after that specific count that can be seen from the drop down list, you won't be able to select your user-import role.
Thus, recreate another role and have its name start with 'a'.

iii) I didn't know there is a limit of 50 uploads. I used up all my upload allocation and had to request for an increase. If you only upload a few users under scenarios such as a few users were failed to be uploaded or other reasons, you should use admin_create_user API.

Method 2: Admin_create_user API

1) Write a script to query your database to retrieve your users and use admin_create_user api to create a user in aws cognito

Note:
i) If you write a python script to create users this way, it would take an amount of time directly proportional to the number of user base as it creates a user at a time. This seems to be a preferred way to use when you want to upload just a few users.

ii) If you don't want aws to send a message to your users, please remember to set the SUPPRESS for MessageAction parameter.

iii) Users created under this way will have a status of FORCE_CHANGE_PASSWORD. This means it requires a specific user login flow.
user logs in using a temporary password set by you / generated by aws
> user resets password (using Amplify Auth, it would be completeNewPassword API)

If you wonder how many user status there could be, you can check out this aws cognito doc
Possible statuses: 
UNCONFIRMED | CONFIRMED | ARCHIVED | COMPROMISED | UNKNOWN | RESET_REQUIRED | FORCE_CHANGE_PASSWORD

iv) Note that using this method and method 3, you can execute other logic such as adding your users to a cognito group, for example. 

Method 3: User migration lambda

This user migration lambda is triggered via user login(UserMigration_Authentication) and forgot password flow(UserMigration_ForgotPassword). This seems to introduce the least interruption to the login flow.

You can check out these aws documents for more detailed information:
i) Importing Users into User Pools With a User Migration Lambda Trigger
ii) Migrate User Lambda Trigger
iii) Migrating Users to Amazon Cognito User Pools by aws mobile

The Node.js code snippet below which is taken from the second aws document above gives an outline of your code for user migration lambda should be.
Note that authenticateUser function should be replaced by your own user authentication code. 

in Node.js
exports.handler = (event, context, callback) => {

    var user;

    if ( event.triggerSource == "UserMigration_Authentication" ) {

        // authenticate the user with your existing user directory service
        user = authenticateUser(event.userName, event.request.password);
        if ( user ) {
            event.response.userAttributes = {
                "email": user.emailAddress,
                "email_verified": "true"
            };
            event.response.finalUserStatus = "CONFIRMED";
            event.response.messageAction = "SUPPRESS";
            context.succeed(event);
        }
        else {
            // Return error to Amazon Cognito
            callback("Bad password");
        }
    }
    else if ( event.triggerSource == "UserMigration_ForgotPassword" ) {

        // Lookup the user in your existing user directory service
        user = lookupUser(event.userName);
        if ( user ) {
            event.response.userAttributes = {
                "email": user.emailAddress,
                // required to enable password-reset code to be sent to user
                "email_verified": "true"  
            };
            event.response.messageAction = "SUPPRESS";
            context.succeed(event);
        }
        else {
            // Return error to Amazon Cognito
            callback("Bad password");
        }
    }
    else {
        // Return error to Amazon Cognito
        callback("Bad triggerSource " + event.triggerSource);
    }
};


Note:
1) At the moment when I did user migration, Amplify library didn't support USER_PASSWORD_AUTH flow. It was default to USER_SRP_AUTH authentication flow.

If you are interested in reading more about that, you can check out this list of Amplify Github USER_PASSWORD_AUTH issues.
This is the Amplify GitHub user authentication issue that I participated in.

2) As of now, feel free to use Amplify to do it as this feature has been included after this Amplify pull request #1033.

List Users

Note that it wouldn't be as easy to filter for your users or query for your users once they are in Cognito compared to using a database.

1) You can search for your users using aws cognito console. For example, you can search for a user using their email.

2)  You can list and filter your users using Amazon Cognito Identity Provider ListUsers API.
Note that it returns a maximum of 60 users. And, you can only filter to the following list of attributes but not custom attributes
  • username (case-sensitive)
  • email
  • phone_number
  • name
  • given_name
  • family_name
  • preferred_username
  • cognito:user_status (called Status in the Console) (case-insensitive)
  • status (called Enabled in the Console) (case-sensitive)
  • sub

Summary

I uploaded my users using csv files and use admin_create_user in lambda to handle edge cases. This way provides security as when I am ready to switch over to cognito, I know my users are already in the cognito. I don't have to worry about migration failure or edge cases. 

Thanks for reading!
Jun