When MFA always is enabled and if user doesn't have any factors enabled other than email. Then email OTP should be prompted before letting user register another MFA

Due to some business requriements we turned off user signup and create users in Auth0 using Management API. We found out that the only way to setup password/MFA for users in this scenario is using reset password token.

With this approach, how can we have the initial change password screen to actually display as Set Password?

Once the password is setup, after the next immediate login MFA enrolment option gets prompted. Is there a way to force user to go through Email MFA before giving this MFA enrolment option for better security reasons. Esp for scenarios like a user sets the inital password and logs back after a long time.

If we try to customize the login-actions to achieve this challengeWith email always then we are loosing many out of box benefits, one of them is passkey registration screen provides Remind me later/not on this device options which seems to be not possible with actions.

Also we noticed that when there’re no factors enabled, email factor type is not returned in the enrolledFactors, is this a bug in our tenant or an expected behaviour?

exports.onExecutePostLogin = async (event, api) => {
const enrolledFactors = event.user.enrolledFactors || [];
const enrolledTypes = new Set(enrolledFactors.map((factor) => factor.type));

console.log(JSON.stringify({
userId: event.user.user_id,
emailVerified: event.user.email_verified,
enrolledFactorTypes: enrolledFactors.map((factor) => factor.type)
}));

// Logs clearly indicate that even if the user email is verified the email factor is not returned

{
"userId: username
“emailVerified”: true,
“enrolledFactorTypes”: []
}

Hi @karuissobusy,

Welcome back to the Auth0 Community!

I understand that you are requiring some clarification on how to change the “Reset Password” text or use the Email MFA. Below is a detailed breakdown of how to achieve your goals, recommended configurations, and a bit of background on how Auth0 handles email verification.

You can easily modify the “Reset Password” Screen to read “Set Password” directly inside your Auth0 Dashboard without needing to write custom code, nor use the Management API:

  1. Navigate to Branding > Universal Login - Edit text and translations in the Auth0 Dashboard.
  2. Select the prompt that best fits, such as Login or reset-password
  3. You can directly edit the text fields for the page title, description, and the button itself to say “Set Password” instead of “Reset Password”.

You can check out the Customize Universal Login Text Elements documentation.

To force a user to verify an email MFA challenge first (especially when logging back in after a long time) without losing out-of-the-box enrollment benefits like the passkey “Remind me later” options, you should utilize Auth0’s native Adaptive MFA or MFA Step-Up API instead of custom logic that intercepts and hijacks the entire flow.

Recommended path:

  1. Go to Security > Multi-factor Auth and set “Require Multi-factor Auth” to Never. This prevents Auth0 from running its default, rigid enrollment flow immediately after first login.

  2. Handle the Login Flow with a Post-Login Action:
    Instead of using api.multifactor.enable(), you can selectively call api.authentication.challengeWith() or api.authentication.challengeWithAny()

    • If a user logs in (even for the first time after a long time), you can challenge them strictly using the email factor first

    • Once they successfully verify their email code, the Post-Login Action resumes. If you detect they have no other high-security factors enrolled (e.g., they have no WebAuthn/Passkey enrolled), you can then trigger api.authentication.enrollWith({ type: 'passkey' })

    • Utilizing api.authentication.enrollWith() natively maintains out-of-the-box behaviors—such as presenting the “Remind me later” or “Not on this device” options on the Passkey registration screen—because you are handing control back to the native Auth0 UI rather than attempting to render standard prompts through custom-coded form redirects.

You also noticed that even when email_verified: true, the array enrolledFactors does not return "email". This is expected behavior. In Auth0’s architecture, Email is not treated as an explicitly enrollable primary MFA factor.

The behavior you observed where enrolledFactors is empty even though email_verified is trueis expected behavior and is not a bug in your tenant.

In modern identity and access management (IAM), Email is not considered a “true” or robust secondary MFA factor (unlike TOTP authenticator apps, SMS, or WebAuthn/Passkeys). Email relies on possession of an inbox, which can be easily compromised if a password is reused, and it doesn’t provide the same cryptographic proof of possession as dedicated authenticators.

Because of this architectural distinction, Auth0 handles email verification differently than true MFA enrollments:

  • enrolledFactors only lists authenticators that a user has actively, explicitly registered as a dedicated secondary factor (e.g., a specific YubiKey, a registered phone number, or Google Authenticator).

  • Email is considered an implicit capability tied to the primary user profile. If a user has a verified email address (email_verified: true), Auth0 inherently knows they are capable of receiving email challenges. Therefore, users do not technically “enroll” in email MFA the way they do with true secondary factors.

You can simply correct your action’s code to enforce your email challenge by simply checking the event.user.email_verified property instead of searching the enrolledFactors array:

exports.onExecutePostLogin = async (event, api) => {  
  const hasVerifiedEmail = event.user.email_verified;
  
  // If the user has a verified email, we can step-up challenge them via Email
  if (hasVerifiedEmail) {
    api.authentication.challengeWith({ type: 'email' });
  }
};

You can also check out this additional documentation articles:

I hope this helps and if you have further questions please let me know!
Best regards,
Remus