Possibility to Enroll in Email MFA or OTP

Overview

This article clarifies whether it is possible to give users a choice between enrolling in Email MFA or OTP, such as Google Authenticator.

Applies To

  • Multifactor Authentication (MFA)
  • MFA Factors

Cause

As explained in this article, Email MFA is not considered a true additional factor, so more secure factors will default to being enrolled when available.

Solution

If there is a need to allow the user to choose, they would still need to enroll in another factor, such as OTP, to be given a choice, but on subsequent logins, there are two options of flow:

  1. If triggering MFA in an Action using “api.multifactor.enable(‘any’)”, the user will be prompted to enroll with OTP for a first-time login (or the most secure factor enabled on the tenant). On return visits, however, a button to Try another method is made available, where, provided the user has verified their email previously, they can select email
  2. Use the Custom MFA Selection feature in Actions to provide more control over what factor is enrolled or challenged and when:

E.g. the action below would (optionally) allow a user to not be prompted for MFA until they have verified their email (and thus have Email MFA enrolled).
It would also check to see if they had enrolled OTP and enroll this if it was missing. Once enrolled with both OTP and Email, on return visits, the user would be presented with a selection dialogue:

exports.onExecutePostLogin = async (event, api) => {
  if (!event.user.email_verified) { //Optional check to skip MFA for unverified emails, allows Email MFA to be enrolled before OTP
    console.log("User not verified, skipping MFA");
    return; //End action early
  }
  if (event.user.enrolledFactors.length) {
    //User has an existing enrolment, challenge it:
    console.log("Enrolled in:",event.user.enrolledFactors);
    api.authentication.challengeWithAny(event.user.enrolledFactors.map(m => ({type: m.type})));
    if (!event.user.enrolledFactors.some(m => m.type === 'otp')) { //If OTP is not yet enrolled, enroll it now
      api.authentication.enrollWithAny([{type: 'otp'}]);
    }
  } else {
    console.log("No enrolment yet")
    api.authentication.enrollWithAny([{type: 'otp'}]); //Email cannot be specified for enrolment due to it being automatic for verified emails
  }
}