Enabling Email as an MFA method

Hi, I am looking for an answer about how to allow our users to select between OTP and email during the MFA challenge. I have been back and forth on this with Auth0 support, and have utilized AI tools as well. I am 99.999% sure that I have configured things correctly, but no matter what I do, email does not get recognized as a valid mfa method. My users are email_verified, and have an email address, and therefore are enrolled.

The kink in this setup is this, and it probably makes a difference:

We are acting as IDP in our application. A user goes to our app and logs in. if they are authenticated, we set a partially authenticated state and redirect the user to Auth0, with a SAML Assertion that authenticates them with Auth0. If their user does not exist in Auth0, they are created on the fly. An Action then challenges them for their MFA. If they are a new user, they get properly challenged for a OTP (and if SMS is enabled they get a link to try that instead). If they pass, they are redirected back to our app with a SAML Assertion that validates that they have passed the MFA, and we clear the state flag in our application and allow them in as fully authenticated.

My understanding is, that on subsequent MFA challenges, they should have the option of choosing email, but that link never appears. I can interrogate all sorts of things in the action indicating that the user has email_verified, and that email is a valid authentication method for them, but when it comes to actually exercising the email MFA, if the action allows it, they are deferred to OTP, and if the action does not allow it, they get an error:

Unknown multifactor provider undefined

I am pretty sure this is a question that can only be answered by a developer who knows the definitive conditions under which the alternate mfa link would be displayed. Please understand that I have configured everything multiple times with multiple permutations, with the direct guidance of Auith0 support, so unless there is some subtle setting I am missing, it is not necessary to paste the standard email mfa config or Action script here. However, if you have experience with this kind of setup, and/or a pertinent piece of information, I would really appreciate knowing it.

Thanks!

1 Like

Hi @cbuxbaum

Welcome to the Auth0 Community!

Please allow me some time to research the matter and I will return with an update for you.

Best regards,
Gerald

Is your relaying party a custom built app of yours? Are you sending the MFA challenge type in the request?

Hi Jonathan,

I’m not utilizing the API. We are doing IDP initiated SAML, with our app acting as the IDP. Auth0 is the Service Provider, and because we have a signed SAML assertion, we are able to authenticate on the fly without an Auth0 login screen. However, we have set up to always challenge MFA, in a post-login Action, and that is where Auth0 should be throwing up the factor picker to let us choose between OTP and email, which it never does, and errors with “Unknown multifactor provider undefined” if we try to force an email challenge in the action.

Using

api.multifactor.enable(‘any’, { allowRememberBrowser: false });

works fine to challenge with otp or phone, but never gives us the opportunity to switch between factors.

Hi @cbuxbaum

One detail stood out to me, which is that you’ve mentioned using api.multifactor.enable(‘any’, { allowRememberBrowser: false }); in order to accomplish your goal. This object will only instruct the flow to enable and require MFA from the user authenticating during that flow and my recommendation for a more suitable option would be to test using api.authentication.challengeWithAny(factors), please see this documentation, as it’s designed to present the user with a list of MFA factors from which they can choose. Another option worth testing out would be api.authentication.challengeWith([{ type: 'factor1'}, { type: 'factor2' }]} depending on your integration, one might work while the other might fail.

I hope my understanding was correct based on the initial context and your previous reply, but let me know if this worked better for you. In case you are still seeing the same behaviour, please test out without an Action presenting the MFA picker, but instead by making the following changes:

  • in your tenant, navigate to Security → Multi-factor Auth;
  • enable OTP and Email as factors;
  • select Always require MFA;
  • under Additional settings, toggle on " Show multi-factor Authentication Options";
  • ensure factors such as Actions do not interfere with the Authentication flow.

Let us know if in your tests you are able to get the correct behaviour using one of the above methods. Also, thank you for also reaching out to Auth0 Support via a case for this!

Best regards,
Gerald

Hi Gerald, thank you for taking the time for this. I have tried these specific challenges, and they frankly don’t work in the context of what we are doing, perhaps because we don’t utilize username/password authentication into Auth0. I still think there is a bug when doing SAML with Auth0 as the SP, that results in the error page

Unknown multifactor provider undefined

whenever we try to call out specific factors for a challenge or enrollment.

However, I will detail our workaround below, utilizing forms as suggested by Auth0 for the email challenge, and still maintaining

api.multifactor.enable(‘any’, { allowRememberBrowser: false });

in the action for otp enrollment and challenge.

Replying to my own question with our workaround, in case it helps anyone else.

Here is our action:

exports.onExecutePostLogin = async (event, api) => {

  api.samlResponse.setAttribute('mfa_passed', 'false')

const enrolledFactors = event.user.enrolledFactors;

if (enrolledFactors && enrolledFactors.length > 0) {

console.log('User Enrolled Factors:', JSON.stringify(enrolledFactors, null, 2));

const isOtpEnrolled = enrolledFactors.some(factor => factor.type === 'otp');

if (!isOtpEnrolled) {

api.multifactor.enable('any', { allowRememberBrowser: false });

api.transaction.setMetadata('mfa_complete', 'true');

return;

}

}

if (event.user.mfa_email === 'true')

{

api.prompt.render('ap_8cDH62MbNTAFGP2b5GpZLt');

if (event.prompt != undefined && event.prompt.vars != undefined)

{

api.samlResponse.setAttribute('mfa_passed', event.prompt.vars.mfa_complete)

console.log(event.prompt.vars.mfa_complete)

}

}

else{

api.multifactor.enable('any', { allowRememberBrowser: false });

}

}




/**

* @param {Event} event - Details about the user and the context in which they are logging in.

* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.

*/

exports.onContinuePostLogin = async (event, api) => {

if (event.user.mfa_email === 'true')

{

if (event.prompt != undefined && event.prompt.vars != undefined && event.prompt.vars.mfa_complete !== 'true' && event.transaction?.metadata?.mfa_complete !== 'true')

{

console.log(event.prompt.vars.mfa_complete);

api.multifactor.enable('any', { allowRememberBrowser: false });

}

else{

return;

}

}

}

and here is the exported json for our form which we started from the email otp template

{
  "version": "4.0.0",
  "form": {
    "name": "Email OTP verification with Auth0 Email Provider",
    "languages": {
      "primary": "en"
    },
    "nodes": [
      {
        "id": "step_qJEB",
        "type": "STEP",
        "coordinates": {
          "x": 599,
          "y": -615
        },
        "alias": "Email verification",
        "config": {
          "components": [
            {
              "id": "rich_text_Cqas",
              "category": "BLOCK",
              "type": "RICH_TEXT",
              "config": {
                "content": "<h2>Check your email</h2><p>Please enter the 6-digit verification code that was sent to <strong>{{context.user.email}}</strong>. The code is valid for 5 minutes.</p>"
              }
            },
            {
              "id": "verification_code",
              "category": "FIELD",
              "type": "NUMBER",
              "label": "Verification code",
              "required": true,
              "sensitive": false
            },
            {
              "id": "next_button_BtEe",
              "category": "BLOCK",
              "type": "NEXT_BUTTON",
              "config": {
                "text": "Continue"
              }
            },
            {
              "id": "resend_button_7eih",
              "category": "BLOCK",
              "type": "RESEND_BUTTON",
              "config": {
                "active_text": "Didn't receive the code?",
                "button_text": "Resend",
                "waiting_text": "Resend in {{remaining_seconds}} seconds",
                "text_alignment": "CENTER",
                "flow_id": "#FLOW-1#",
                "max_attempts": 3,
                "waiting_time": 60
              }
            }
          ],
          "next_node": "flow_hq3b"
        }
      },
      {
        "id": "flow_hq3b",
        "type": "FLOW",
        "coordinates": {
          "x": 1202,
          "y": -204
        },
        "config": {
          "flow_id": "#FLOW-2#",
          "next_node": "$ending"
        }
      },
      {
        "id": "flow_3Qle",
        "type": "FLOW",
        "coordinates": {
          "x": 337,
          "y": -50
        },
        "config": {
          "flow_id": "#FLOW-1#",
          "next_node": "step_qJEB"
        }
      },
      {
        "id": "step_5cJB",
        "type": "STEP",
        "coordinates": {
          "x": 169,
          "y": 558
        },
        "alias": "New step",
        "config": {
          "components": [
            {
              "id": "choice_GAHd",
              "category": "FIELD",
              "type": "CHOICE",
              "label": "MFA Factor",
              "required": false,
              "sensitive": false,
              "config": {
                "multiple": false,
                "options": [
                  {
                    "label": "OTP Authenticator",
                    "value": "otp"
                  },
                  {
                    "label": "Email",
                    "value": "email"
                  }
                ]
              }
            },
            {
              "id": "next_button_xA1f",
              "category": "BLOCK",
              "type": "NEXT_BUTTON",
              "config": {
                "text": "Continue"
              }
            }
          ],
          "next_node": "router_5UHb"
        }
      },
      {
        "id": "router_5UHb",
        "type": "ROUTER",
        "coordinates": {
          "x": 734,
          "y": 346
        },
        "alias": "Choose  MFA Factor",
        "config": {
          "rules": [
            {
              "id": "id_5853828829579",
              "alias": "Email",
              "condition": {
                "operands": [
                  {
                    "operands": [
                      "{{fields.choice_GAHd}}",
                      "email"
                    ],
                    "operator": "EQ"
                  }
                ],
                "operator": "AND"
              },
              "next_node": "flow_3Qle"
            },
            {
              "id": "id_9874204690555",
              "alias": "Otp",
              "condition": {
                "operands": [
                  {
                    "operands": [
                      "{{fields.choice_GAHd}}",
                      "otp"
                    ],
                    "operator": "EQ"
                  }
                ],
                "operator": "AND"
              },
              "next_node": "flow_2V1u"
            }
          ]
        }
      },
      {
        "id": "flow_2V1u",
        "type": "FLOW",
        "coordinates": {
          "x": 1203,
          "y": 255
        },
        "alias": "New flow",
        "config": {
          "flow_id": "#FLOW-3#",
          "next_node": "$ending"
        }
      }
    ],
    "start": {
      "next_node": "step_5cJB",
      "coordinates": {
        "x": -197,
        "y": 41
      }
    },
    "ending": {
      "resume_flow": true,
      "coordinates": {
        "x": 1709,
        "y": -205
      }
    }
  },
  "flows": {
    "#FLOW-1#": {
      "name": "Generate OTP & Send email",
      "actions": [
        {
          "id": "generate_otp",
          "type": "OTP",
          "action": "GENERATE_CODE",
          "allow_failure": false,
          "mask_output": false,
          "params": {
            "reference": "{{context.user.email}}",
            "length": 6
          }
        },
        {
          "id": "send_email",
          "type": "AUTH0",
          "action": "SEND_EMAIL",
          "allow_failure": false,
          "mask_output": false,
          "params": {
            "to": "{{context.user.email}}",
            "subject": "{{custom_vars.code}} is your verification code",
            "body": "{% if user.nickname %}\n<p>Hello {{user.nickname}},</p>\n{% else %}\n<p>Hello,</p>\n{% endif %}\n<p>{{custom_vars.code}} is your verification code</p>",
            "custom_vars": {
              "code": "{{actions.generate_otp.code}}"
            }
          }
        }
      ]
    },
    "#FLOW-2#": {
      "name": "Verify OTP & Update user",
      "actions": [
        {
          "id": "verify_otp",
          "type": "OTP",
          "action": "VERIFY_CODE",
          "allow_failure": false,
          "mask_output": false,
          "params": {
            "reference": "{{context.user.email}}",
            "code": "{{fields.verification_code}}"
          }
        },
        {
          "id": "if_then_condition_mnXT",
          "alias": "Is it valid?",
          "type": "FLOW",
          "action": "BOOLEAN_CONDITION",
          "allow_failure": false,
          "mask_output": false,
          "params": {
            "if": {
              "operands": [
                {
                  "operands": [
                    "{{actions.verify_otp.valid}}",
                    true
                  ],
                  "operator": "EQ"
                }
              ],
              "operator": "AND"
            },
            "then": [
              {
                "id": "update_user_IPZP",
                "type": "AUTH0",
                "action": "UPDATE_USER",
                "allow_failure": false,
                "mask_output": false,
                "params": {
                  "connection_id": "#CONN-1#",
                  "user_id": "{{context.user.user_id}}",
                  "changes": {
                    "email_verified": true
                  }
                }
              },
              {
                "id": "store_shared_variable_ggJG",
                "alias": "mfa_complete",
                "type": "FLOW",
                "action": "STORE_VARS",
                "allow_failure": false,
                "mask_output": false,
                "params": {
                  "vars": {
                    "mfa_complete": "true"
                  }
                }
              }
            ],
            "else": [
              {
                "id": "show_error_message_Whan",
                "type": "FLOW",
                "action": "ERROR_MESSAGE",
                "allow_failure": false,
                "mask_output": false,
                "params": {
                  "message": "We're sorry, your verification code is not valid. Please, review it and try it again."
                }
              }
            ]
          }
        }
      ]
    },
    "#FLOW-3#": {
      "name": "check choice",
      "actions": [
        {
          "id": "if_then_condition_alzN",
          "alias": "check choice",
          "type": "FLOW",
          "action": "BOOLEAN_CONDITION",
          "allow_failure": false,
          "mask_output": false,
          "params": {
            "if": {
              "operands": [
                {
                  "operands": [
                    "{{fields.choice_GAHd}}",
                    "otp"
                  ],
                  "operator": "EQ"
                }
              ],
              "operator": "AND"
            },
            "then": [
              {
                "id": "store_shared_variable_k9gD",
                "alias": "mfa_complete",
                "type": "FLOW",
                "action": "STORE_VARS",
                "allow_failure": false,
                "mask_output": false,
                "params": {
                  "vars": {
                    "mfa_complete": "false"
                  }
                }
              }
            ],
            "else": [
              {
                "id": "store_shared_variable_wxGS",
                "alias": "mfa_complete",
                "type": "FLOW",
                "action": "STORE_VARS",
                "allow_failure": false,
                "mask_output": false,
                "params": {
                  "vars": {
                    "mfa_complete": "true"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  },
  "connections": {
    "#CONN-1#": {
      "id": "ac_eXPm5kRYH1GtndQA84jrET",
      "app_id": "AUTH0",
      "name": "REPLACE_WITH_M2M_CONNECTION"
    }
  }
}