With the recent announcement of deprecation of various Realm services, we have been looking at alternatives. I thought I’d present our approach to user migration with the thought that it may be useful for members of the community who are struggling with how to approach the problem but also looking for a second set of eyes to see if anyone has a better approach. It occurs to me that there are likely very many highly skilled engineers all trying to solve the same problems and it would be great to start sharing our solutions…
We consider here moving to AWS Cognito, although other authentication providers will follow a similar path. The approach is in two phases, where Realm acts as the primary user store then Cognito becomes the primary user store. Cognito was chosen for the following reasons:
- Cognito integrates well with AWS Amplify which we had recently migrated to from Realm hosting
- Managing Cognito through Amplify allows for multiple branches, user pools and integration with GitHub (and other repositories) for easy deployment and branch management.
- Some (limited) support for user migration workflows.
Phase 1 - Realm is primary user store. We run this for several months to give as many users as possible time to log in. The objective is to migrate as many users as possible into the Cognito store with correct passwords. The Cognito store will contain usernames (i.e. emails), passwords and an identifier to the Mongo user _id. In line with best practise, Realm does not allow the export of passwords and AWS Cognito does not allow the import of passwords.
We consider the following use cases:
- User sign-up - we should not intercept sign-up as at this stage the email address has not been validated and we don’t want to put unvalidated email addresses into the Cognito user store. Having unvalidated users in the Cognito store prevents the migration scripts from triggering.
- User validates email address - whilst this would be a good step to intercept and create a user in Cognito, the Realm email link provides no mechanism to identify the user and so it cannot be used to link to any user in the Cognito user pool.
- User login - Change front end to also attempt login to Cognito which triggers a lambda migration function to create new user in Cognito. Password is known and available.
- Password reset request - User requests reset email from Realm. No ability to intercept this process on Realm side and password is unknown. Could use Cognito to send the reset email which can be customised more; however, this won’t work if the user is not in the Cognito user pool yet. Both Realm and Cognito attempt to email the user and we don’t want the user to get two reset password emails. Therefore we propose to request Cognito to delete the user if it is known. It’s a compromise but having no user is better than having a user with the wrong password.
- Password reset confirm - User clicks on reset email link from Realm and is navigated to reset page. The link cannot be used to identify the user. The password is known at this point and can be used to reset Realm user’s password. However, it cannot be used to reset the Cognito user’s password as we cannot determine who the user is. Therefore take no further action
Phase 2 - Cognito becomes the primary user store. The objective is to migrate all remaining users but without a valid password; remaining users will be forced to go through password reset flow. This phase comes later and gives time to migrate the Realm functions to lambda functions. The main issue here is that the existing Realm / Mongo integration has functionality to ensure logged in users can only access their own data. This will have to be implemented in lambda functions in such a way as to protect from potential defects giving access to the wrong user’s data.
- Export all users from Mongo user collection and import into Cognito. Users will be in an unconfirmed state and will not be able to login until their password is set.
- Switch the front-end login process to use the Cognito pool exclusively.
- Message remaining users and explain that they will need to reset their passwords. Add additional messaging to the site to say that if you have not logged in for a while then you will need to reset your password. This may be an opportunity to encourage lapsed users to re-engage.
- User attempts to login - this will fail and they will be invited to go through the password reset process. Password reset process uses the Cognito flow exclusively.
As an alternative consider going straight to phase 2. This will need careful messaging to ensure that users are not lost in the transition.
Open questions:
- The process above does not consider users who have not completed email verification. Is there any way to extract these?
- Is it possible to do an admin reset password in Realm - i.e. reset the password of any user as an administrator or system user? If this were possible then password resets could be handled by Cognito and a lambda function that would then instruct Realm to change the users password. It is possible for a logged in user to change the password in Realm but that is not sufficient.
- Is it possible to identify the user from the codes given in the Realm reset password email? If it is then this information could be used to instruct Cognito to reset the user’s password.