Handle authentication/password resets for unique usernames with duplicate email address using CIC (Auth0)

TL;DR

In certain unique cases, I have come across a scenario where end customers are using a unique username (ex: USER01) with duplicate email address. In this blog post, I have tried to document a solution/approach on how we could handle authentication and password reset for these users using CIC (Auth0).

Known Information

Currently CIC (Auth0) enforces customer identities to use unique email address (as on May 2023)

Solution Overview

  • Continue using the customDB (ex: MongoDB) has the user repository for authentication/password reset.
  • Enable “Requires Username” within Custom DB connection settings in CIC
  • Do not enable “Import Users to Auth0” within Custom DB connection settings in CIC
  • Use “Database Action Scripts” to achieve authentication and password resets functionality.

Sequence Diagram of the solution

Implementation Steps

Create a DB Connection in CIC for your custom DB(ex: In my case, I have created it in MongoDB)

  • Enable toggle “Requires Username”
  • Haven’t enabled “Import Users to Auth0”
  • Enable toggle “Disable Sign Ups”

Under “Custom Database” tab

  • Enable “Use my own database”

Update the “Database action script” for Login action <ex: sharing code snippet>

function login(email, password, callback) {
  const bcrypt = require("bcrypt");
  const MongoClient = require('mongodb@4.1.0').MongoClient;
  const dbUser = configuration.dbUser;
  const dbPwd = configuration.dbPwd;
  const dbHost = configuration.dbHost;
  const dbName = configuration.dbName;
  const dbCollection = configuration.dbCollection;

  const uri = `mongodb+srv://dbuser:cxxxxx@mongodbcluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority`;
  const client = new MongoClient(uri);

  client.connect(function(err) {
    if (err) return callback(err);

    const db = client.db('kjmongodb');
    const users = db.collection('kjmongodb');
    console.log("db-->" + db);
    console.log("users-->" + users);

    users.findOne({ username: email }, function(err, user) {
      if (err || !user) {
        client.close();
        console.log("err- findOne-->" + err);
        return callback(err || new WrongUsernameOrPasswordError(email));
      }

      if (user) {
        console.log('Password value is on If true-->' + user.password);
      } else {
        console.log('Password value is on Else true-->' + user.password);
      }
      console.log('Password value entered as input-->' + password);

      bcrypt.compare(password, user.password, function(err, isValid) {
        client.close();
        if (isValid) {
          console.log('Password is correct');
        } else {
          console.log('Password is incorrect');
        }

        if (err || !isValid) {
          console.log("err- compare-->" + err);
          return callback(err || new WrongUsernameOrPasswordError(email));
        }

        return callback(null, {
          user_id: user._id.toString(),
          username: user.username,
          email: user.email
        });
      });
    });
  });
}

Update the “Database action script” for Get User action <ex: sharing code snippet>

function getByEmail(email, callback) {
  const MongoClient = require('mongodb@4.1.0').MongoClient;
  const dbUser = configuration.dbUser;
  const dbPwd = configuration.dbPwd;
  const dbHost = configuration.dbHost;
  const dbName = configuration.dbName;
  const dbCollection = configuration.dbCollection;

  const uri = `mongodb+srv://dbuser:cxxxxx@mongodbcluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority`;
  const client = new MongoClient(uri);

  client.connect(function(err) {
    if (err) return callback(err);

    const db = client.db('kjmongodb');
    const users = db.collection('kjmongodb');

    users.findOne({ 'username': email }, function(err, user) {
      client.close();

      if (err) return callback(err);
      if (!user) return callback(null, null);

      return callback(null, {
        user_id: user._id.toString(),
        username: user.username,
        email: user.email
      });
    });
  });
}

Update the “Database action script” for Change Password action <ex: sharing code snippet>

function changePassword(email, newPassword, callback) {
  const bcrypt = require('bcrypt');
  const MongoClient = require('mongodb@4.1.0').MongoClient;
  const dbUser = configuration.dbUser;
  const dbPwd = configuration.dbPwd;
  const dbHost = configuration.dbHost;
  const dbName = configuration.dbName;
  const dbCollection = configuration.dbCollection;

  const uri = `mongodb+srv://dbuser:cxxxxx@mongodbcluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority`;
  const client = new MongoClient(uri);
  let username = email.split('@')[0];

  client.connect(function(err) {
    if (err) return callback(err);

    const db = client.db('kjmongodb');
    const users = db.collection('kjmongodb');

    bcrypt.hash(newPassword, 10, function(err, hash) {
      if (err) {
        client.close();
        return callback(err);
      }

      users.update({ username: username }, { $set: { password: hash } }, function(err, count) {
        client.close();
        console.log(err, count);
        if (err) return callback(err);
        callback(null, true);
      });
    });
  });
}

Unit Testing

Login into your web application or use the ‘Try Connection’ from the custom db settings in CIC

Lets try performing “forgot password”

Click ‘Continue’

Check for a password reset email from CIC (Auth0), In my case I had go to user4@mailinator.com email <Email should be something as the below screenshot>

Click ‘Confirm’ button in the email link, where you would be redirected to CIC (Auth0) page to set a new password.

Now you can login using the newly reset password.

Summary

This approach summaries that, using CIC (Auth0) we could easily handle user authentication and password resets for customers who use unique usernames with duplicate email addresses within your application/stack.

References

2 thoughts on “Handle authentication/password resets for unique usernames with duplicate email address using CIC (Auth0)

  1. Reading through this article, I do not think you actually solved the multiple email problem. While a user can login via typing their unique username, the issue becomes the password reset. Your change password action script assumes that the left portion of the email will define the username. i.e. “user4@mailinator.com” means username is “user4”. This is absolutely a one to one relationship between user and email address and does not cover the case of multiple email addresses being associated to different usernames. If you have say “user4” and “anotheruser4” that both use the email “user4@mailinator.com”. You would only be changing the password for “user4” because it’s username matches. You are completely unable to change the password for “anotheruser4”. Not to mention, in your specific case, if usernames don’t line up with email you couldn’t change the password at all. Think the case of “user4” and “user5” sharing the email address of “CoolUser@mailinator.com”. It just won’t work.

    I believe what it really comes down to is that if you have multiple users associated with the same email address, Auth0 has to provide additional information about the user during password reset to be able to correctly identify the user.

    1. Hi Ryan,
      Thanks for your comment and time.

      In this document, the example user (user4@mainilinator.com) is a duplicate email address used by other users within my MongoDB. I can confirm if you try to changing the password for username (user4), the password reset email from Auth0 gets sent out to email address (user4@mailinator.com) as scripted in “Change Password” DB Action script. When the user performs the password reset for username (user4) the password gets changed only to that specific username and it does not impact other usernames within the MongoDB.

      In my blog, I have kept the use-case(simple) to derive email address for Change Password DB action based on the username input.

      The probable solution to your usecase could be something like this,
      1. Create a custom password reset page using the auth0.js library and then change the link in the classic universal login lock widget forgotPasswordLink link to point at the custom password reset page you can provide your own link for a password reset flow. see this documentation (https://auth0.com/docs/libraries/lock/lock-configuration)
      2. The custom password reset page would have to look up the user based on the username e.g user4 or anotheruser4 to get the real email address in the app_metadata
      Note: alternatively, you can save this parameter in the id_token using a action when the user logs in to save that call to the management API in the future.. e.g email verification ticket
      3. Once we have the real email address you have to create a password reset ticket using the management api. Please see this documentation (https://auth0.com/docs/api/management/v2/tickets/post-password-change)

      Thanks
      KJ

Leave a Reply