Migrating Off Meteor Accounts

Has anyone here had any experience migrating a site using Meteor accounts to a node based system? I’m looking to pull my login system out of Meteor on one of my projects, but I can’t find any info on anyone doing this while maintaining their accounts.

It should merely be copying over the relevant account data to the corresponding places for the new account system. Passwords may need to be recreated by your users at their next login.

If you can provide more info about which account packages (social, passwords etc) you’re using and the target platform you’re migrating to.

User id’s may sometimes become an issue based on the data types but again nothing a mapping (ETL) across your database won’t solve.

And perhaps if you have specific questions?

I’ve done migrations both to and from meteor with little trouble. In fact it can be a good opportunity to do some data cleaning, too!

1 Like

Thanks for the response. I guess my intentions would be to do it without having the users recreate their passwords unless it’s an invisible process to the user. I’m not using any service based packages, just email/password accounts.

When you migrated off, did you use a new auth service or did you just roll your own?

Glad to hear it’s not much trouble. I’m going to start messing around with it this week.

1 Like

I have done migrations to both homegrown systems and to 3rd party provided ones like libraries and services.

Whether you’ll need to reset passwords depends how source and target platforms are using encryption on those passwords.

Since meteor’s is open souce, you know what the source does:

  • hashes the password on sha256 on the client
  • encrypts it using bcrypt on the server
  • compares passwords from login attempts on bcrypted values

Here’s the server side implementationf from meteor: https://github.com/meteor/meteor/blob/52532e70e53631657d55aa60409bca6f3e243922/packages/accounts-password/password_server.js#L73

If you’re lucky your target platform does the same, or provides a mechanism to hook into the process.

Worst case scenario, you can do an extended migration where users logging in on the new system the first time have their passwords compared with the existing version and then immediately updated to the new version, again using some sort of middleware utility.

You should also beware that one arguably strong opinion in password security is that if your database is handed over from one system to another system, you should invalidate them anyway, having your users create new passwords.

3 Likes

@stolinski what did you wind up doing?

Easy thing to do would be to send password reset emails to everyone using the new system, and delete the services.password and services.resume fields of the users collection.

If that’s too much of a user disruption, Auth0 has a seamless migration, where you’d authenticate against the old Meteor server first, and if successful, then create an Auth0 email/password account:

1 Like

@loren I basically decided that I didn’t have the bandwidth to deal with it just yet. I’d still like to make this happen. I was thinking a good solution would be rolling a version of the Meteor email login system myself that’s the exact same as Meteor’s without Meteor itself on npm. I haven’t explored what it would take to make that happen though.

Thanks! Writing an apollo-based package based on accounts-js that does the equivalent of accounts-password with the same MongoDB users table format. Will report back when it’s ready :slight_smile:

For oauth based accounts, like accounts-facebook, you could either base it on accounts-js oauth, or do an Auth0 integration and just match the (verified) sub in Auth0’s jwt with user.services.facebook.id.

I’ve done something similar. I’m aiming to get off of it completely but currently I have a 2nd server that authenticates the meteor login token from the client’s local storage.

Here’s my current setup:

Meteor app handles login/password reset
React app looks for Meteor’s loginToken on local storage
sends this token on every request to the non meteor server
meteor server gets a user object from mongo by converting login token to hashed token
(code for this below if it’s helpful)

I also have the ability to create a new user with an email account on this non meteor server, without having to reset all the passwords. The code below will take a plain text password and re-implement the meteor email password logic (a bit easier if you’re already using node!). I converted it to an equivalent in Elixir but you can prob. just copy/paste their source code directly to do this. Worth noting, you need to use the older Bcrypt $1a strategy in order to not need a password reset.

The only thing missing for me to completely migrate away from Accounts would be the generating a token (which can be replaced with JWT easily), and querying the server for a user object instead of using Meteor.user(). Lastly updating the UI so uses that queried user data (no small feat with the Meteor.user calls sprinkled throughout lol).

Hope this helps! I found that creating email users was surprisingly quick.

code for re-using password (note %{foo: ...} is the same as a JS object):

defmodule Accounts.User do

  def create(params, :client) do
    random_id = make_random_mongo_id()
    timestamp = make_bscon_timestamp(:now)
    # create a password hash Meteor can use (SHA'd password with Bcrypt $1a)
    hashed_pass =
      params.password
      |> MyApp.Crypto.sha256  #make sha256 hash from plaintext
      |> Base.encode16(case: :lower)
      |> Comeonin.Bcrypt.hashpass(Comeonin.Bcrypt.gen_salt(12, true))

    user_doc = %{
      _id: random_id,
      createdAt: timestamp,
      services: %{password: %{bcrypt: hashed_pass}},
      emails: [ %{address: params.email, verified: false} ],
      roles: ["client"],
      info: %{firstName: params.first_name, lastName: params.last_name},
      tempPhone: params.phone,
      phone: params.phone, # for meteor sms login
      appData: %{}
    }

    Mongo.insert_one("users", user_doc)
  end
end
  def get_user_by_token(auth_token) do
    hashed_token = :crypto.hash(:sha256, auth_token) |> Base.encode64
    query = %{"services.resume.loginTokens.hashedToken" => hashed_token}
    {:ok, user} = get_user(query)
    case user do
      %{email: email, id: id} ->
        Logger.info("User: #{email} - #{id}")
      _else ->
        IO.puts "User not logged in"
    end
    user
  end

You can overwrite the Meteor.user function sometime early in your app, use Tracker for signaling an invalidation (or whatever you’re doing), and populate the user document whenever you’d like.

I ended up forking the accounts-base and accounts-password packages to use my internal auth format, and wrote a database migration. If you continue to use bcrypt and SHA256 a user’s password before bcrypt-ing it, everything just works.

3 Likes

Made an npm package based off of accounts-js and apollo that uses the same users collection format, so it’s a drop-in replacement for meteor/accounts-password:

5 Likes

Wow! I can’t wait to check this out. Looks amazing.

1 Like

Hello guys … I am also trying to get the answer to this. I am trying to authenticate a user through nodejs but when I try to access the “services” field in the document of users collection I get undefined. I have asked for help in the forum here is my post Access user.services may be you guys can help me figure this out. Thanks in advance