[object Object]

How we moved 6 Million Users from Auth0 to Firebase

We successfully moved more than 6 million user accounts from Auth0 to Firebase. Silently. Without anyone noticing.

POSTED ON NOVEMBER 16, 2020 BY THOMAS

Case study: Why we moved millions of Mimo app users from Auth0 to Firebase

Over the last weeks we have successfully moved more than 6 million user accounts (Mimo app users) from Auth0 to Firebase. Silently. Without anyone noticing. Without logging anyone out. Take a look at the following chart, which shows the activity on our platform during the transition.

Learn to code app - Transition graph

In this post I talk about how we achieved this and what challenges we had to face. Including the timeline of the rollout and some technical details. But first let me explain why we even had to do it.

Why we left Auth0

Mimo is a consumer product teaching the young as well as the old how to code with nothing more than their smartphone. Over the last months, we’ve increased the amount of content offered for free, which led to a growing number of users. While Auth0 was a great partner for handling account management, they are not the best fit for a consumer heavy platform like Mimo app. At the end, Auth0’s pricing model was not sustainable for us. We had to move elsewhere.

What such a change means for a consumer product like ours

App vs. web

The majority of our users learn with us on their phones. Compared to web applications, people are not used to getting logged out and logging back in again. We do already get a good amount of customer support requests for forgotten login credentials. So, very early on, we decided that logging everyone out is not an option, we needed to make sure existing users don’t notice the underlying change and stay logged in.

"Forcefully logging everyone out would be a huge hit for our retention and overwhelm our customer support"

How sensitive is our data actually

We are taking security seriously, but the measures taken need to be aligned with how sensitive data is. Mimo app is not a bank or an insurance company, the sensitivity of the data we store in our system is not comparable. We made sure users data is secured at all times, but keep this in mind for the following sections, it will help understand why we chose specific flows.

What the challenges were

Keeping users logged in

As explained above, we had to find a way to keep users logged in while moving to Firebase. Technically, Auth0 and Firebase are very similar when it comes to distributing JWTs and how their authentication works in general. But it was clear that we could not simply import user accounts to Firebase and expect everything to just work. The problem here is that while a valid access token is still authenticating users in our backend, the refresh token Auth0 issued can not be used to get a new access token from Firebase.

"We had to find a way to exchange an Auth0 refresh token for a refresh token from Firebase"

Luckily, the Firebase client and admin SDKs provide APIs to implement what we needed (Client SDK & Admin SDK). In general, the flow is rather simple.

  1. Import Auth0 accounts to Firebase
  2. Clients call our custom API endpoint with a newly refreshed Auth0 access token
  3. The endpoint verifies and validates the token, looks up in Firebase if the user exists and, if so, generates a custom token. That token gets sent back to the Client.
  4. The Client uses the Firebase Admin SDK’s custom login token API to get a new access token and refresh token
Mimo learn to code
admin
  .auth()
  .getUser(userId)
  .then(() => {
      return admin.auth().createCustomToken(userId, {});
  })
  .then((customToken) => {
      return response.json({
          userId: userId,
          firebaseToken: customToken,
      });
  })
  .catch((error) => {
      return response
          .status(404)
          .send({ error: "User not found in firebase" });
  });

We are aware that this flow might not be suitable for applications with highly confidential data. But as described above, for our case it is a secure enough implementation.

User accounts between export and successful migration

Getting 6 million user accounts exported from Auth0 is a manual task. After you request that export someone from Auth0 provides a secure download. This creates another problem though — how are we going to capture new signups happening between the export and the final rollout? We used two strategies here, let’s talk about the easier one first.

Social Accounts

Auth0 provides a way to react on different events like logins or signups. We implemented a simple hook which would import a single account to Firebase in the event of a new signup.

function (user, context, callback) {
    if (user.identities.length !== 1 || user.identities[0].isSocial === false) {
        // we only move non linked social accounts
        // we can't move them at all if they are linked with email/pwd because we can't get pwd
        callback(null, user, context);
        return;
    }

    // detect signups and only move forward in case it is one
    var today = new Date();
    var diffSeconds = null;
    diffSeconds = (today - Date.parse(user.created_at)) / 1000;

    if(diffSeconds > 30) {
        callback(null, user, context);
        return;
    }

    var data = {
        user_id: user.user_id,
        email: user.email,
        email_verified: user.email_verified === true,
        name: user.name,
        picture: user.picture,
        created_at: user.created_at,
        last_login: user.last_login,
        identities: user.identities,
    };

    // call the custom import endpoint
    var url = '/import-function';
    request.post({
        url: url,
        json: data
    }, function (e, r, b) {});

    callback(null, user, context);
}

Password Accounts

Password Accounts are more complicated. There is no way to move these accounts on the fly since only the manual export gives access to the password hashes. The only way is to keep accepting and using Auth0 until at some point we request a second export just for these users.

Keeping Auth0 for a transition period

Building a native product for iOS and Android means that you can only partially control what app versions your users have installed. After a new release it takes some time until the adoption is high enough for ignoring older versions. For us this means still supporting Auth0 for some time.

Data mapping

Before user accounts can be imported to Firebase we need to transform some properties on the exported data. In addition we have cases where multiple accounts have the same email attached. This is simply not possible in Firebase anymore so filtering the data is necessary. In order to pick the correct active account we had to lookup users in our database and find the account that was active recently.

The day of the migration

We had the following very rough plan:

  • Release the backend to production. This version can handle Auth0 and Firebase access tokens
  • Enable the Auth0 rule to move over new Auth0 signups to Firebase on the fly
  • Request the export from Auth0
  • Transform and clean the exported Accounts to an importable state
  • Run the import
  • Release the apps

From the time we started the migration to the moment we could release our apps, the transition took us around 8 hours. Importing 6 million accounts through Firebases API takes some time. By now everything is finished and most of our users are authenticated through Firebase. We did not run into any noticeable issues, which makes this project a real success.

I want to thank the whole Mimo team for pulling this off. The last two weeks have been exhausting and the manual testing required high concentration. Thanks to Silvia, Samat, Irina, Filip and Diego for the effort! This would not have been possible without such a great team.

Stay up to date with our latest blog posts and our awesome team, because we value lifelong learning at Mimo.

If you want to download the Mimo app, go on App Store or Google Play and sign up for our free trials.

POSTED ON NOVEMBER 16, 2020 BY THOMAS

Learn to code and land your dream job in tech

Start for free