Enforce Unique Phone Number per User for SMS MFA

Problem statement

Auth0 allows multiple users to register the same number of SMS MFA.

Explain why this may happen and if it is possible to enforce any checks to determine whether the same number is shared across multiple user accounts.

Cause

In general terms, the users in separate connections (databases) are considered different unrelated users, even if the details such as email, phone number email are the same. For example, one user instance resides in the Regular Database connection (auth0|…) and another in the Passwordless Database connection (sms|…).

Multiple accounts can have the same phone number registered as MFA, even within the same connection. There is currently no way to prevent this from happening.

Solution

There is no simple out-of-the-box approach for this use case due to the fact that there is no available method to search users by phone numbers (the phone_number field is used by Passwordless connections and not for MFA enrollments). For more information, refer to Search Query Syntax - Searchable Fields

The only way to look up a number that is used for MFA is to iterate through every user calling the endpoint Get List of Authentication Methods . However, that approach would also require disabling phone number obfuscation as mentioned in this Community post . This is a very intensive operation every time a user tries to enrol a new phone number, as every existing user would have to be interrogated behind the scenes before allowing the enrolment to go ahead.

The best way to accomplish this is to use the “Send Phone Message” Action trigger, which could be set to filter only enrolment attempts. It could lookup “taken” phone numbers on a customer-hosted database, then throw an error if there is a match. Here is an example of how this could implemented in the Action:

exports.onExecuteSendPhoneMessage = async (event, api) => {
  if (event.message_options.action === 'enrollment') { //only run for enrollments
     const result = // make await call to external database here, passing the value of event.message_options.recipient and returning a boolean result
    if (result === true) {
      throw new Error("Number in use");
    } else {
       //update external database with new number
    }
  }
// call your SMS provider here
};

The downside to using the Action is that the Classic Universal Login experience does not show an error. It quietly fails the enrollment (although a tenant log is created with the error text provided). By contrast, the New Universal Login will throw an error to the end user and the error message text can be customized. For more information, refer to Customize New Universal Login Text Prompts