TL;DR: In this article, you will learn how to set up MongoDB Atlas as a custom database in your Auth0 account. First, you will learn how to configure your custom database to migrate users to Auth0 (which will end up improving the security of your app and the privacy of your users), then you will learn how to keep your MongoDB Atlas cluster up to date (i.e., keep your users in there).

"If you choose to use Auth0, you can gradually migrate your users without disruption."

Preparing the MongoDB Atlas Cluster

If you already have a MongoDB Atlas database cluster that you would like to use, feel free to skip to the Whitelisting IP addresses section. However, if you need to set up a test environment, follow the instructions described here.

To create a new MongoDB Atlas cluster, head to their website and use the get started free form to create a new account (or you can sign into an existing one if you prefer). When you get into your MongoDB Atlas dashboard, click on the build a cluster button. After clicking on it, the dashboard will present you a form that you can fill in like this:

  • Cloud Provider & Region: AWS, N. Virginia (us-east-1)
  • Cluster Tier: M0 (Shared RAM, 512 MB Storage)
  • Additional Settings: MongoDB 4.0, No Backup
  • Cluster Name: Cluster0

These settings will give you a brand new cluster that is free forever (but be aware that this cluster is not good enough for production, just for testing). So, after filling in this form, click on the create cluster button to finish the process.

How to Create a MongoDB User

Now that you have your MongoDB cluster up and running, the next thing you will do is to create a database user to access it. To do so, head to the Security tab of your cluster and click on the Add New User button. When you click on this button, the dashboard will ask you to:

  • Enter a username: Here you can add something like auth0-custom-db-user.
  • Enter a password: Here you will have to define a password for this user (you can use the Autogenerate secure password to help you on this).
  • Set User Privileges: Here you will have to choose the Read and write to any database option.

Note: Make sure you copy the password somewhere safe, you will need it in a few moments.

After filling in this form, click on the Add User button. The process might need a few seconds to take effect.

Whitelisting IP addresses

As explained by the docs, MongoDB Atlas only allows client connections to the cluster from entries in the project’s whitelist. As such, you will need to go to the IP Whitelist section of the Security tab of your cluster and add the IP addresses that Auth0 uses. When you reach this section, click on the Add IP Address button and use the form to add addresses. You will need to use this form to add all the ip addresses that Auth0 uses, one by one.

Note that, depending on where you chose to create your tenant, the set of IP addresses will vary. To confirm what addresses you will need to add, please, check this resource.

For example, at the time of writing, Auth0 was using the following IP addresses for tenants based on their US region:

  • 35.167.74.121
  • 35.166.202.113
  • 35.160.3.103
  • 54.183.64.135
  • 54.67.77.38
  • 54.67.15.170
  • 54.183.204.205

Note: If you don't have an existing database on MongoDB Atlas, you will also have to whitelist your local IP address so you can connect to the cluster from your development environment to create the database. To add your local address, click on the Add IP Address button, then use the Add Current IP Address option.

Whitelisiting Auth0's IP addresses on MongoDB Atlas.

Preparing the Database

If you already have a database with users and passwords, you can skip this section. Otherwise, you will have to add a collection into your cluster with credentials, so that you can test the integration between Auth0 and Atlas. To do so, you will need to use a MongoDB client application (like MongoDB shell or MongoDB Compass) to connect to your cluster and to create this collection.

To connect your client application to Atlas, open your cluster details in the dashboard and click on the Connect button. When you click on this button, Atlas will show you three options:

  • Connect with the Mongo Shell: You can choose this one if you feel comfortable on the terminal.
  • Connect Your Application: You would choose this one if you were making a custom app connect to the cluster (not the case here).
  • Connect with MongoDB Compass: You can choose this one if you prefer using MongoDB Compass.

Follow the instructions on one of these options to connect to the cluster. After connecting to it, you will have to create a database on it (you can call it auth0-integration), and you will have to create a users collection inside this database.

If you are on MongoDB Compass, you can use the Create Database button to achieve that. If you are on MongoDB Shell, you can issue use auth0-integration to create the database.

Once the database is created, you can start adding application users to your cluster. For example, if you are on MongoDB Shell, you can issue the following query to add three users:

db.users.insert(
   [
     { username: "a.user@spam4.me", password: "som3Passw0rd!" },
     { username: "another.user@spam4.me", password: "newPassw0rd!" },
     { username: "yet.another.user@spam4.me", password: "an0th3rPwd!" }
   ]
)

If you are on MongoDB Compass, you will have to click on the users collection, then you will have to click on the Insert Document button. Clicking on this button will make MongoDB Compass show a dialog where you will have to input the details of the users manually, one by one.

Inserting application users as document with objectid and string with MongoDB Compass

How to Use Auth0 Custom Databases and Migrate Users

After preparing your MongoDB Atlas cluster, you are now ready to configure Auth0 to use this cluster as a custom database. To achieve that, you will need to sign into your Auth0 dashboard, and head to its Database Connections section.

Note: If you don't have one yet, you can sign up for a free Auth0 account here.

Inside this section, click on the Create a DB Connection button. Then, on the New Database Connection form, insert a name to your new connection (something meaningful like atlas-custom-db) and click on the Create button.

After creating the database connection, head to its Custom Database section and turn on the Use my own database option. Clicking on it will make the area below this option available. As you can see there, Auth0 will enable you to customize how the integration will work on different scenarios: login, create, verify, change password, get user, and delete.

As the goal now is to import users from MongoDB Atlas into Auth0, you will head back to the Settings tab of your custom connection and click on the Import Users to Auth0 option. Then, if you open the Custom Database tab once more, you will see that your connection will allow you to customize only two scenarios: login and get user. This happens because everything else will be handled by Auth0, and not by your legacy database anymore.

Now, replace the script for the login action with the following one:

function login(email, password, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "{ATLAS-USER}";
  const dbPwd = "{ATLAS-PWD}";
  const dbHost = "{ATLAS-HOST}";
  const dbName = "{ATLAS-DB}";
  const usersCollection = "{USER-COLLECTION}";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    if (err) return callback(err);

    const collection = client.db(dbName).collection(usersCollection);

    collection.findOne({username: email, password}, (err, user) => {
      if (err) return callback(err);
      if (!user) return callback("User not found.");

      const profile  = {
        ...user,
        user_id: user.username
      };
      callback(null, profile);
      client.close();
    });
  });
}

Note: The scripts provided on thi blog post use a modern JavaScript syntax. If you encounter problems trying to execute them, go to your tenant settings, then open the Advanced tab, and check if the Runtime field is using Node 8.

There are a few important things that you must understand about this code:

  • The script you are defining is actually a function that receives three parameters: email, password, and callback. You will use the first two parameters (email and password) to validate the user against your legacy database. Then, you will use the callback parameter to end this function (successfully or not).

  • This script depends on version 3.1.4 of the mongodb library. You need this specific version because the others available on Auth0 do not support MongoDB Seed Lists (a feature used by Atlas).

  • This script defines five constants that you will have to update: dbUser, dbPwd, dbHost, dbName, and usersCollection. The first two (dbUser and dbPwd) are the credentials of the database user you created earlier, the latter (usersCollection) is the name of the collection where you inserted application users, and the other two (dbHost and dbName) are the identifier of your cluster and database on MongoDB Atlas. Make sure to update these constants accordingly.

Note: The dbHost constant will look like cluster0-a1ig3.mongodb.net.

  • After connecting to your cluster, this function gets a reference to the user collection and uses it to issue a query to findOne user with the email and password passed to this function. If an error occurs, or if no user with these credentials is found, the function ends with an error (i.e., the authentication process fails). Otherwise, the function creates a profile object and passes it to the callback function.

Passing a profile as the second argument to the callback function will make the login script end successfully. That is, if you call the callback function with a profile, your user will successfully log in, and its details will migrate from your Atlas cluster to Auth0.

Note: The script above is considering that you are persisting passwords as plain text in your MongoDB Atlas cluster. This approach is not recommended in any situation and it is used here just to enhance readability and to smooth the learning process. If you have an existing custom database that you want to use, you will probably have to adjust the script to meet the password encryption schema used there.

To confirm that everything is in order, click on the try button above the script, then use the credentials of one of the application users. If your script works as expected, you will see the profile of the user in this screen. You can also try to use an invalid set of credentials (i.e., a combination of email and password that does not exist). Doing so will make this screen show a message saying: "User not found."

Trying connection between MongoDB Atlas custom database and Auth0 authentication.

Enabling Users to Reset Their Passwords

Now that you have confirmed that the login script is working, the next thing you will have to do is to define the get user script. Auth0 will use this script to validate that users that have not been migrated yet do exist in the custom database. This will happen before executing flows that do not require authentication (i.e., signup and password reset).

To define this script, move to the Get User tab and insert the following code into the code editor:

function getByEmail(email, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "{ATLAS-USER}";
  const dbPwd = "{ATLAS-PWD}";
  const dbHost = "{ATLAS-HOST}";
  const dbName = "{ATLAS-DB}";
  const usersCollection = "{USER-COLLECTION}";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    if (err) return callback(err);

    const collection = client.db(dbName).collection(usersCollection);

    collection.findOne({username: email}, (err, user) => {
      if (err) return callback(err);
      if (!user) return callback(null);

      delete user.password;
      const profile  = {
        ...user,
        user_id: user.username
      };
      callback(null, profile);
      client.close();
    });
  });
}

As you can see, this script is extremely similar to the login script. The differences are:

  • Instead of using email and password to find a user, this script uses only the email.
  • To avoid passing back to Auth0 the password of the user, this script deletes it (delete user.password).

To test this script, click on the Save button, then click on the Try button and use the form on the dialog. There, if you use a valid email, you will see the profile associated with it.

Custom Databases without User Migration

Another alternative that you have while using Auth0 custom databases is to keep your users' data in your own servers (in other words: to avoid user migration). This alternative gives you better control over this data but, on the other hand, gives you also the burden of having to deal with the security and privacy of your users' data.

After considering the pros and cons of this alternative, if you still feel like this is the way to go, Auth0 can help you keep the data in your own data stores. To achieve this, you will need to start by opening the Settings tab of your database connection (on the Auth0 dashboard) and ticking off the Import Users to Auth0 checkbox. Then, if you go to the Custom Database tab one last time, you will have to implement each one of the authentication steps: login, create, verify, change password, get user, and delete.

"Auth0 does not force you to migrate your user data to their databases (although you should, as it is more secure)."

Implementing the Other Scripts

First of all, you would need to implement the login script. However, as you already did so in the last section, you can skip this one and move to the Create script. There, you can use the following code as a reference:

function create(user, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "{ATLAS-USER}";
  const dbPwd = "{ATLAS-PWD}";
  const dbHost = "{ATLAS-HOST}";
  const dbName = "{ATLAS-DB}";
  const usersCollection = "{USER-COLLECTION}";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    const collection = client.db(dbName).collection(usersCollection);

    collection.insertOne(user, (err, result) => {
      if (err) return callback(err);
      if (result.insertedCount !== 1) return callback("Unable not inserted.");

      callback(null);
      client.close();
    });
  });
}

Note: Just like on the other scripts, you will have to configure dbUser, dbPwd, dbHost, dbName, and usersCollection with your Atlas' details.

The behavior of this script is quite similar to the other two. The difference is that this one uses the insertOne function to insert a user on your MongoDB Atlas cluster. Note that, to let Auth0 know that this script ended successfully, you have to call callback(null);.

Note: It is very important to change the script above to hash and salt the password before persisting it to the database. You must never store passwords as plain text. Check this resource to learn how to hash and salt passwords on Node.js.

After implementing this script, you can move on to the Verify tab and use the following code as a reference:

function verify(email, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "{ATLAS-USER}";
  const dbPwd = "{ATLAS-PWD}";
  const dbHost = "{ATLAS-HOST}";
  const dbName = "{ATLAS-DB}";
  const usersCollection = "{USER-COLLECTION}";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    const collection = client.db(dbName).collection(usersCollection);

    collection.updateOne({username: email}, {$set: {email_verified: true}}, (err, result) => {
      if (err) return callback(err);
      if (result.modifiedCount !== 1) return callback("Unable to mark user as verified.");

      callback(null, true);
      client.close();
    });
  });
}

Note: Just like on the other scripts, you will have to configure dbUser, dbPwd, dbHost, dbName, and usersCollection with your Atlas' details.

The goal of this script is to flag a user as having confirmed that they are the owner of the email address they claim to own. That is, Auth0 will call this script whenever a user clicks on the verification link sent by email. To finish this script successfully, you have to call callback(null, true);, which indicates to Auth0 that the script has successfully marked the user as having a verified email address.

Next, you can head to the Change Password tab and use the following script as a reference to implement this step:

function changePassword(email, newPassword, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "{ATLAS-USER}";
  const dbPwd = "{ATLAS-PWD}";
  const dbHost = "{ATLAS-HOST}";
  const dbName = "{ATLAS-DB}";
  const usersCollection = "{USER-COLLECTION}";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    if (err) return callback(err);
    const collection = client.db(dbName).collection(usersCollection);

    // TO-DO: salt and hash password before persisting it (check note below)

    collection.updateOne({username: email}, {$set: {password: newPassword}}, (err, result) => {
      if (err) return callback(err);
      if (result.modifiedCount !== 1) return callback("Unable to change user password.");

      callback(null, true);
      client.close();
    });
  });
}

Note: It is very important to change the script above to hash and salt the password before persisting it to the database. You must never store passwords as plain text. Check this resource to learn how to hash and salt passwords on Node.js.

Just like the Verify script, this one updates a user based on its email address. The difference to the other is that, here, you are updating the password property of the user. Make sure you configure dbUser, dbPwd, dbHost, dbName, and usersCollection with your Atlas' details.

After defining the Change Password script, the next thing you will have to do is to redefine the Get User one. The new version of this script will have to trigger callback(null) in case no user is found on the database (the previous version was triggering callback("User not found.")). Now that you are using your database to store all user data, Auth0 will call this script on different scenarios (like on sign up to check if another user is already using the requested email address). Therefore, this script cannot raise an error when no user is found anymore (otherwise, Auth0 would end the calling process prematurely).

So, open the Get User tab and use the following code as a reference to update this script:

function getByEmail(email, callback) {
  const {MongoClient} = require("mongodb@3.1.4");
  const dbUser = "{ATLAS-USER}";
  const dbPwd = "{ATLAS-PWD}";
  const dbHost = "{ATLAS-HOST}";
  const dbName = "{ATLAS-DB}";
  const usersCollection = "{USER-COLLECTION}";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    if (err) return callback(err);

    const collection = client.db(dbName).collection(usersCollection);

    collection.findOne({username: email}, (err, user) => {
      if (err) return callback(err);
      if (!user) return callback(null);

      delete user.password;
      const profile  = {
        ...user,
        user_id: user.username
      };
      callback(null, profile);
      client.close();
    });
  });
}

With that in place, the last thing you will have to do is to define the script that removes users from your database. To do so, open the Delete tab and use the following script as a reference to implement it:

function remove(id, callback) {
  const {MongoClient, ObjectID} = require("mongodb@3.1.4");
  const dbUser = "{ATLAS-USER}";
  const dbPwd = "{ATLAS-PWD}";
  const dbHost = "{ATLAS-HOST}";
  const dbName = "{ATLAS-DB}";
  const usersCollection = "{USER-COLLECTION}";

  const uri = `mongodb+srv://${dbUser}:${dbPwd}@${dbHost}/test?retryWrites=true`;
  const client = new MongoClient(uri, { useNewUrlParser: true });

  client.connect(err => {
    if (err) return callback(err);

    const collection = client.db(dbName).collection(usersCollection);

    collection.deleteOne({_id: ObjectID(id)}, (err, result) => {
      if (err) return callback(err);
      if (result.deletedCount !== 1) return callback("Unable to delete user.");

      callback(null);
      client.close();
    });
  });
}

That's it! After implementing the Delete script, you have finished configuring Auth0 to use your MongoDB Atlas as a custom database (without migrating users).

To test these scripts, you can either use the Try button that Auth0 shows on each one of the script tabs, or you can follow the instructions on the Auth0 Docs to learn how to secure your apps with Auth0. Nevertheless, if you need help, don't hesitate using the comments box below to reach out to us.

Troubleshooting

If you encounter problems during the configuration of your custom database scripts, you can open the Extensions section of your Auth0 dashboard and install Real-time Webtask Logs. After installing it, this extension will show on the Installed Extensions tab. If you open this extension, after signing into it, you will be able to see any console.log call you make on the custom database scripts.

Troubleshooting custom database connections on Auth0 with Real-time Webtask Logs.

"Using MongoDB Atlas as a custom database in Auth0 is easy."