Customize MFA Selection for Universal Login

Auth0 supports a variety of factors for securing user access with multi-factor authentication (MFA). Using post-login Actions, you can customize your MFA flows to challenge users with a specific factor or sequence of factors. You can also use contextual information about users and their organizations to create more individualized experiences. For example, you can customize your flows to challenge users with specific factors based on their membership in certain Organizations or their assigned user roles.

How it works

You can use Actions to customize your MFA flows. Specifically, you can modify the post-login trigger of the Login Flow with the following Authentication API methods:

  • challengeWith: Specifies the factor or factors users must use to authenticate, such as a one-time password (OTP). This method presents a default challenge to users and can optionally provide access to a factor picker that allows them to choose a different authentication method.

  • challengeWithAny: Sets a group of factors users can choose from when authenticating, such as email and OTP. By default, this method presents a factor picker to users rather than a specific challenge, in accordance with the following conditions:

    • If two or more factors are specified, a factor picker displays to the user.

    • If the user has only enrolled in one of the specified factors (or only one factor is supplied), the factor picker is skipped.

    • If the user has not enrolled in any of the specified factors, the command fails.

You can use a combination of these methods to tailor your MFA flows as needed. You can also incorporate user metadata, such as roles or previously used factors, into these methods to create more tailored flows.

When choosing MFA challenges for your commands, you can use the factors listed below or the enrolledFactors value. enrolledFactors represents the list of active factors associated with a user's account.

  • otp

  • email

  • push-notification

    • otpFallback

  • phone

    • preferredMethod: voice

    • preferredMethod: sms

    • preferredMethod: both

  • webauthn-platform

  • webauthn-roaming

The array event.authentication.methods includes a type field when the name of the method is set to mfa. type is a string that contains factor values matching those used by the type field from enrolledFactors (listed above). When an MFA challenge is performed, methods contains an object of name:mfa with type set to the factor used for that challenge. methods is only updated when an Action begins. To see the results of a challenge, methods must be accessed in the next Action in the flow.

To learn more, review the following resources:

Sequenced and contextual flows

With the challengeWith or challengeWithAny commands, you can use contextual information to determine the best challenge or series of challenges to present to users. Specifically, you can leverage the following:

  • Sequenced flows: Challenge users with a series of different factors in a specific order.

  • Contextual flows: Determine which factor to next challenge the user with based on previous challenges in the flow.

To help illustrate these flows, consider the following example:

// ACTION 1 

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

   api.authentication.challengeWithAny([{ type: 'phone'}, { type: 'push-notification' }]);

} 

// ============================================ 

// ACTION 2 

// Decide based on what the user did in the previous action 

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

    if(event.authentication.methods.find(m => m.type === 'phone') && event.authorization.roles.some('admin')) { 

        api.authentication.challengeWith({ type: 'push-notification' }); 

    }

}

Was this helpful?

/

In this scenario, a user is first challenged with SMS via the challengeWithAny command in Action 1. Then, Action 2 challenges the user with a push notification because they have the Admin user role and also completed the SMS challenge.

In this flow, you can make decisions about which factor to challenge the user with due to the following:

  1. The flow pauses after executing Action 1.

  2. The user completes the MFA flow prompted by Action 1.

  3. event.authentication.methods.type in Action 2 populates with information from the previous MFA challenge.

  4. The flow resumes to execute Action 2 using contextual information from Action 1.

While this example presents a similar experience to using redirects in your Actions, commands using challengeWith and challengeWithAny offer the following unique benefits:

  • Flows pause after each command, allowing you to accumulate user information that can be used in subsequent Actions. Comparatively, redirects only occur a single time as the final command in a flow.

  • MFA is triggered after each Action containing challengeWith or challengeWithAny commands is executed. With redirects, MFA runs as the final Action in the pipeline.

Note: This method of executing Actions only applies to those containing challengeWith or challengeWithAny commands. Actions serving other purposes are not affected.

Before you begin

Before you can customize your MFA flows, you must first enable MFA in your tenant and prompt your users to enroll in the appropriate factors.

Prepare your tenant

To get started, set up MFA in your tenant and enable the Customize MFA Factors using Actions setting. You can set up one or more factors and define your MFA policies on the Auth0 Dashboard under Security > Multifactor Auth.

To customize your flows, you must enable the Customize MFA Factors using Actions toggle in the Additional Settings section. Your customized flows will not work properly if this setting is not enabled.

Auth0 Dashboard > Security > Multi-factor Auth > Additional Settings

Enroll users in factors

Once MFA is configured, ensure your users enroll in one or more of the factors you enabled. Users must enroll in authenticators before they can be challenged by post-login Action commands.

After a user signs up or is created in your tenant, you can create enrollments with the Management API authentication-methods endpoint, or you can manage users' enrollments directly through their profile pages in the Auth0 Dashboard.

Customize your MFA flows

Once your tenant is ready, you can create post-login Actions to customize your MFA flows. Steps and example use cases are provided below.

Create your post-login Action

  1. On your Auth0 Dashboard, navigate to Actions > Flows and select Login.

  2. Under Add Action, select Custom and choose Create Action.

  3. On the Create Action popup:

    • Enter a name for your Action.

    • Select Login / Post-Login as the trigger.

    • Use Node 18 (Recommended) for the runtime.

  4. Review the popup to ensure accuracy. Then, select Create.

  5. After creation, the code editor displays the onPostExecute command. Add your custom code or code sample to the command.

  6. When your command is ready, select Deploy.

  7. Select Add to Flow on the successful deployment notification.

    • Note: If the notification has closed, choose Back to Flow above the code editor.

  8. Drag and drop your new command from the Add Action panel into your Login flow. Then, select Apply.

To make additional updates to your Action, navigate to Actions > Library > Custom and select your command. You can then update and redeploy your code as needed.

Test your post-login Action

To ensure your commands function appropriately, you can test your Action through the Auth0 Dashboard:

If the flow is successful, a confirmation screen displays. If you encounter any issues, you can update your code by navigating to Actions > Library > Custom on your Auth0 Dashboard.

Example use cases

The examples below outline common use cases for customizing MFA flows.

Use current enrollments to determine challenge method

The following sample challenges a user with MFA if they are enrolled with the following factors:

  • One-time password (OTP)

  • Phone

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

 api.authentication.challengeWithAny([{type: 'otp'}, {type: 'phone'}]);

}

Was this helpful?

/

Use roles to determine challenge method

The following sample challenges all users with OTP. If a user has the Admin role and requires a higher level of access to your application, they are challenged with an additional factor as a form of step-up authentication.

exports.onExecutePostLogin = async (event, api) => {
    api.authentication.challengeWith({type: 'otp'});

    const isAdmin = event.authorization.roles.some('admin');
    if(isAdmin) {
        api.authentication.challengeWith({type: 'phone'});
    }
}

Was this helpful?

/

Use metadata to determine challenge method

In this example, MFA factors are enabled at the Organization level. This sample uses different categories of metadata to determine the right challenge for individual users:

  • Organization metadata: Organization-level data, such as the specific factors enabled for an Organization.

  • User metadata: User-level data, such as whether a user has a phone number associated with their profile.

exports.onExecutePostLogin = async (event, api) => {
  const orgFactors = event.organization.metadata.factors ?? [];

  // Get the intersection of factors available for the user and factors enabled for the org
  const availableFactors = orgFactors.filter(f => event.user.enrolledFactors.some(ef => ef.type === f));

  // Prefer push if available
  if(availableFactors.includes('push-notification')) {
    api.authentication.challengeWith({ type: 'push-notification' });
    return;
  }

  // If the user has a verified phone number and the organization
  // allows for SMS and email, prefer SMS and allow email as a fallback
  // if available
  if(event.user.phone_number && 
     event.user.phone_verified && 
     availableFactors.includes('phone')) {
    if(availableFactors.includes('email') {
      api.authentication.challengeWith({ type: 'phone' }, {
        additionalFactors: [{
          type: 'email'
        }]
      });
    } else {
      api.authentication.challengeWith({ type: 'phone' });
    }

    return;
  }

  // If push-notifications and/or phone couldn't be prioritized, fallback to email if
  // enabled for the org, otherwise fail.
  if(availableFactors.includes('email') {
    api.authentication.challengeWith({ type: "email" });
    return;
  }

  api.access.deny("No MFA factors available for this org + user");
};

Was this helpful?

/

Allow users to select an alternate method of authentication

For a more flexible experience, you can present users with a Try Another Method link as part of their MFA challenge. This link allows users to select a different method of authentication than the default challenge.

To achieve this, include the additionalFactors parameter in your Actions code. You can set this parameter to a specific factor for all users or use enrolledFactors to let users choose their preferred factor.

Specific Factor

The following sample challenges users with OTP by default. If desired, users can access the Try Another Method link to authenticate with email instead.

exports.onExecutePostLogin = async (event, api) => {
  api.authentication.challengeWith({ type: 'otp' }, 
    { additionalFactors: [{type: 'email'}] })
};

Was this helpful?

/

Enrolled Factors

The following sample challenges users with OTP by default. If desired, users can access the Try Another Method link to authenticate with one of their other enrolled factors.

exports.onExecutePostLogin = async (event, api) => {
  const enrolledFactors = event.user.enrolledFactors.map((f) => ({type: f.type}));

  api.authentication.challengeWith({ type: 'otp' }, 
    { additionalFactors: enrolledFactors })
};

Was this helpful?

/

Use Adaptive MFA to determine when to challenge users

The following example uses Adaptive MFA to determine if users should be challenged.

Adaptive MFA is a flexible MFA policy that protects your tenant from bad actors by assessing potential risk during login transactions and prompting users for additional verification when appropriate.

In this case, users are prompted with MFA if they log in from an unrecognized device and their overall confidence score is low or medium.

/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @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.onExecutePostLogin = async (event, api) => {
  if (event.authentication?.riskAssessment?.assessments.NewDevice) {

  // Example condition: prompt MFA only based on the NewDevice 
    // confidence level, this will prompt for MFA when a user is logging in 
    // from an unknown device.
    let shouldPromptMfa;

    switch (event.authentication.riskAssessment.assessments.NewDevice.confidence) {
      case 'low':
      case 'medium':
        shouldPromptMfa = true;
        break;
      case 'high':
        shouldPromptMfa = false;
        break;
      case 'neutral':
        // When this assessor has no useful information about the confidence, 
        // do not prompt MFA.
        shouldPromptMfa = false;
        break;
    }

      // It only makes sense to prompt for MFA when the user has at least one 
      // enrolled MFA factor.
    const canPromptMfa = event.user.enrolledFactors?.length > 0;

    if (shouldPromptMfa && canPromptMfa) {
      const enrolledFactors = event.user.enrolledFactors.map((f) => ({type: f.type}));
      api.authentication.challengeWithAny(enrolledFactors);
    }

  }

};

Was this helpful?

/

Use Actions to challenge users

You can use Actions to customize MFA flows by modifying the post-login trigger of the Login Flow. This example uses the phone method of authentication and preferredMethod: 'both', referring to the active MFA factors associated with a user's account. For more information, refer to Actions Triggers: post-login - Event Object.

api.authentication.challengeWith({ 
  type: 'phone', 
  options: { preferredMethod: 'both'} 
});

Was this helpful?

/

Troubleshooting

In the event that you experience errors or unexpected results from your customized MFA flows, you can use the information below to help identify and resolve these issues.

Tenant logs

You can monitor your customized MFA flows through tenant logs.

Tenant logs are available on the Auth0 Dashboard under Monitoring > Logs. Alternatively, you can retrieve logs using the Management API.

If you or your users experience unexpected behavior, review tenant logs for the following event codes for more information:

Scenario Event Code Descriptive Error
A user is prompted with multi-factor authentication, but none of the requested factors can be used as a challenge. In this case, the user cannot complete MFA. mfar This scenario results in the following error message:

An MFA challenge is used in a PostLogin action but the requested factors are not properly set up. To perform MFA, enable the requested factors and ensure the user is enrolled with them.
A user is prompted with multi-factor authentication, but one of the requested factors cannot be used as a challenge. In this case, the user can complete MFA using a different requested factor. w This scenario results in the following warning message:

An MFA challenge is used in a PostLogin action, but the requested factor {factor name} is not properly set up. Enable the requested factor and ensure the user is enrolled with it.

Troubleshooting checklist

The following checklist provides additional suggestions for identifying and resolving common issues with customized MFA flows.

  1. The Customize MFA factors with Actions toggle must be enabled.

  2. Factors referenced in your Actions must be enabled in your tenant.

  3. Users must be enrolled in the factors referenced in your Actions.

    • If an individual receives an error, review their user details to ensure they are enrolled in the proper factors. Navigate to Auth0 Dashboard > User Management > Users and select their name from the list.

      • Review the Multi-factor Authentication section on the Detail tab to verify their enrollments. If the user is not enrolled, you can use the Send an enrollment invitation link available in this section.

      • Alternatively, verify the user's enrollments through the Raw JSON tab. This information can also be retrieved using the Management API. However, it is important to note that the API does not list auto-enrolled authenticators, such as Email factors set up via an email verification link.

    • If users are not enrolled in the appropriate factors, you can create enrollments with the Management API authentication-methods endpoint. You can also manage users' enrollments directly through their profile pages in the Auth0 Dashboard.

  4. Ensure your Actions have been deployed and saved in your Pipeline.

    • Navigate to Auth0 Dashboard > Actions > Library > Custom. Locate your Action in the list and ensure its status is Deployed. If a different status is listed, access your Action, review your code, and click Deploy to the top right.

    • Navigate to Auth0 Dashboard > Actions > Library > Flows and select Login. Ensure your Action is listed in the flow. If not, access the Custom tab of the Add Action panel and drag and drop your Action into your Login flow. Then, select Apply.

  5. Ensure you've upgraded to the latest version of post-login Actions.

    • Navigate to Auth0 Dashboard > Actions > Library > Custom and select your Action. If your Action is out-of-date, you will see a yellow banner prompting you to update the Action. If the banner displays, select Update.

    • You can also specify the latest version of post-login Actions for deployment when using the Deploy CLI. For more information, review Configure the Deploy CLI.