Automatic Migration clarification

I’m banging my head against the wall on automatic migration.

Following the directions here:

I have a SQL Server database with usernames and hashed passwords. I created my custom DB connection in Auth0, and filled out the Login and Get User scripts, even verifying the previous hash algorithm. Everything tests fine.

However, I went to try it in an actual app. Invalid username or password. Some light digging indicates I need to turn off the Auth0 username and password database. So, I do that, everything works. Great.

But… there is no way to register users this way? Aditionally, even though I’m authenticating with the username/password from my old database, it doesn’t seem to “migrate”. The expectation would be if I then change the password in the old database, it has no effect, Auth0 would have “migrated” the user including the password that was used at the time? However, once I change the password in the old DB, everything just gets completely lost and jumbled?

What am I missing here? The documentation on this subject has many incorrect points it seems.

Hello @rev23dev,

You should have no trouble both migrating users from your SQL database and registering new users at the same time. This is how we are set up (LDAP instead of SQL Server, but same model). Once the user is migrated, Universal Login will not attempt to re-migrate the user unless you remove the user from the Auth0 hosted database.

Make sure you have only the connection (Auth0 hosted database) you are migrating to enabled for your app, that usernames are enabled (if you are using usernames), and that you have not disabled sign-ups for that connection.

If you can post screenshots of your connection settings and post your Login and Get User code someplace, that will help with troubleshooting.

So you’re saying no applications should actually connect to the custom database, and only the Username-Password-Authentication database? When I do that, it definitely doesn’t grab users, just says wrong username/password. Though this is how the documentation is written.

If I actually turn that connection on, and the default one off, then it works.

Two connections:

Custom DB Settings, per documentation. Import Users to Auth0 is on, Disable signups is off.

Apps in custom connection turned off, per your comment, as well as no mention of doing it in documentation

The script templates needed some decent reworking as they don’t work as written when connecting to SQL (filed a support ticket for this one). Below are the scripts, both return fine during the tests.

  //this example uses the "tedious" library
  //more info here: http://pekim.github.io/tedious/index.html
  const crypto = require('crypto');
  const sqlserver = require('tedious');

  const Connection = sqlserver.Connection;
  const Request = sqlserver.Request;
  const TYPES = sqlserver.TYPES;

  const connection = new Connection({
    userName:  '[snip]',
    password:  '[snip]',
    server:    '[snip]',
    options:  {
      database: '[snip]',
      encrypt: true
    }
  });

 
  const query = 'SELECT Id, Email, PasswordHash FROM [User] WHERE Email = @Email';
  const delimiter = '*';

  connection.on('debug', function (text) {
    //console.log(text);
  }).on('errorMessage', function (text) {
    console.log(JSON.stringify(text, null, 2));
  }).on('infoMessage', function (text) {
    //console.log(JSON.stringify(text, null, 2));
  });

  // hash passwords using the old method
  function generateSaltedPassword(password, salt) {
    var sha512 = crypto.createHash("sha512");
    sha512.update(salt+password, "utf8");
    var result = sha512.digest("base64");
    return salt + delimiter + result;
  }
   connection.on('connect', function (err) {
    if (err) return callback(err);

    const request = new Request(query, function (err, rowCount, rows) {
      if (err || rowCount < 1) {
        callback(err || new WrongUsernameOrPasswordError(email));
      }
    });

    request.on('row', function(columns) {
      var pwd = columns[2].value;
      var salt = pwd.substr(0, pwd.indexOf(delimiter));

      if (generateSaltedPassword(password, salt) !== pwd) {
        return callback(new WrongUsernameOrPasswordError(email));
      }

      callback(null, {
        user_id: columns[0].value,
        email: columns[1].value
      });
    });

    request.addParameter('Email', TYPES.VarChar, email);
    connection.execSql(request);
  });  
}

function getByEmail(email, callback) {
  const sqlserver = require('tedious@1.11.0');

  const Connection = sqlserver.Connection;
  const Request = sqlserver.Request;
  const TYPES = sqlserver.TYPES;

  const connection = new Connection({
    userName:  '[snip]',
    password:  '[snip]',
    server:    '[snip]',
    options:  {
      database: '[snip]',
      encrypt: true
    }
  connection.on('debug', function(text) {
    // if you have connection issues, uncomment this to get more detailed info
    //console.log(text);
  }).on('errorMessage', function(text) {
    // this will show any errors when connecting to the SQL database or with the SQL statements
    console.log(JSON.stringify(text));
  });

  connection.on('connect', function(err) {
    if (err) return callback(err);

    var user = {};
    const query =
      'SELECT Id, Email from [User] ' +
      'WHERE Email = @Email';

    const getUserQuery = new Request(query, function (err, rowCount) {
      if (err) return callback(err);
      if (rowCount < 1) return callback();

      callback(null, user);
    });

    getUserQuery.addParameter('Email', TYPES.VarChar, email);

    getUserQuery.on('row', function (columns) {
      user = {
        user_id: columns[0].value,
        email: columns[1].value
      };
    });
    
    

    connection.execSql(getUserQuery);
  });
}

If I try to do a password reset, the log just shows the attempt to connect to the Username-Password-Authentication database, and fails. The log is similar for a failed log on attempt.

{
  "date": "2019-06-22T15:47:32.316Z",
  "type": "fcpr",
  "description": "User does not exist.",
  "connection": "Username-Password-Authentication",
  "connection_id": "con_iDECz4HCTYphnjIc",
  "client_id": "tjitirQKZDzO6OfWUKOMft3LU0TB6k07",
  "client_name": "REV23",
  "ip": "107.2.248.153",
  "user_agent": "Other 0.0.0 / Other 0.0.0",
  "details": {
    "body": {
      "tenant": "rev23",
      "client_id": "tjitirQKZDzO6OfWUKOMft3LU0TB6k07",
      "connection": "Username-Password-Authentication",
      "email": "<snip>",
      "verify": true,
      "debug": false
    }
  },
  "user_id": "",
  "user_name": "<snip>",
  "strategy": "auth0",
  "strategy_type": "database",
  "log_id": "90020190622154732316246309427507163561141921458014912626",
  "isMobile": false
}

The other way around. For any given application, enable only the connection(s) relevant to that application. You can mix and match connections and apps as needed.

So enable the REV23-XRMS DB you created for all your REV23* apps, disable the default ‘Username-Password-database’ (or just delete it altogether) for those same apps.

Screenshots look good. You will want to enable the usernames toggle (second screenshot) if you intend to use / import usernames. Auth0’s default behaviour is based on email addresses only. Your code looks like you are just using email though, which is good.

Thanks mark. So, turning off the Username-Password-Authentication db for that app, and only leaving on the custom one does work. The docs fail to explain this.

When I did this last night however, I then changed the password in my custom database after “migrating” the user. Then no password worked. Today it seems ok. I will continue testing.

I also don’t understand this section:
“Once all your users are in the Auth0 database, you are ready to convert the database to Auth0…”

I have user accounts that are 10 years old, some of which are probably just orphaned at this point and unlikely to be migrated, but need to leave them as they have orders attached to them.

Should I just leave this custom connection available for some amount of time, and then bulk import everyone that didn’t migrate and disable this connection? What happens if after the migration this connection is deleted entirely? Right now in the users section it shows migrated users with this connection name… if that connection gets deleted… what happens?

Actually, I just tested the delete. All migrated users go with it. So… no one should do that :laughing: The docs tell you what to do, but don’t tell you what not to do. Wouldn’t hurt to mention this.

The language / terminology on that page and around ‘connections’ in general is confusing. A ‘connection’ created under Connections → Database can be an Auth0 hosted database, an external database (SQL, LDAP, noSQL), or an Auth0 hosted database into which you are importing identities from an external database.

If it is an external database (without import into Auth0) you must provide the code for all CRUD operations (create, delete, change password, etc.). If you are importing, you only need to provide the Login and Get User scripts (these two are nearly identical, which is important). In the import scenario you will leave your Login / Get User code in place for as long as you feel is appropriate to import your active users, after which you either bulk import the rest or leave them in the legacy DB or delete them, whatever is appropriate for your scenario.

An odd side effect of import is you cannot ever really turn it off. You just replace the Login and Get User scripts with nop scripts. The line about “converting the database to Auth0” really just means removing the code that communicates with your external DB, replacing it with the nop scripts. Confusing!

@markd thank you very much for your input today. it’s much appreciated. just wanted to say that.

So there will never be a scenario where I use the standard Username-Password-Authentication database it seems and can just be deleted to avoid confusion? Any new users that are created will likewise go to this new db connection.

The docs give the sense that it will look for the user in your custom database, then migrate to the built-in username-password-authentication database. Meaning, you need both. Not the case. I think that’s where a lot of the confusion came from.

I think it’s starting to come together for me. Like you said, there is a lot of confusing stuff in these docs. The docs for getting up and running on a new app are brilliant… once you dive deeper there is a lot to be desired.

That’s correct. The Username-Password-Authentication database / connection is just a default / example database that every new Auth0 tenant has. Completely safe to delete it, and you can create as many of your own databases / connections as you like.

The documentation is often a challenge. In Auth0’s defence, there’s a lot of it (last time I checked the content under Auth0 alone was about 780,000 words) and Auth0 moves pretty fast so I’ve no doubt it’s hard to keep up. That said, you can submit doc bugs here to highlight specific problems.

Happy to help!

2 Likes