How to know which MFA factor was used, synchronously at token issuance (not via async log queries)?

We’re using New Universal Login with a custom domain.

After login, we trigger step-up MFA from a Post-Login Action:

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

At the callback, we verify the tokens on the server side.

I’m trying to determine the MFA factor the user completed, for example, WebAuthn, OTP, or Push, synchronously during the login/token issuance flow.

I’ve already confirmed that:

  • event.authentication.methods only shows { name: "mfa", timestamp }

  • amr only includes "mfa", not the specific factor

  • challengeWith / challengeWithAny Resume in the next Action, but still only expose "mfa"

  • Management API logs do show the factor, but they are async.

Is there any supported synchronous way to access the completed MFA factor and surface it as a token claim, or is the only supported option to either rely on Management API logs or force a single factor with challengeWith({ type })?

Hi @nadav.beladev

Yes, there is a supported synchronous way to access the specific completed MFA factor (e.g., webauthn-platform, otp, push-notification) and surface it as a token claim.

However, you cannot use api.multifactor.enable() to achieve this. You must use the pipeline-pausing api.authentication.challengeWithAny() method in one Action, and then read the explicitly documented type property from event.authentication.methods in a second, subsequent Action.

To get the granular factor synchronously, you must explicitly take control of the MFA routing using the newer challengeWith / challengeWithAny commands. These commands uniquely pause the Action flow, challenge the user, and then resume the flow, populating the exact factor used.

According to the Auth0 Custom MFA Selection Documentation:

"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… When an MFA challenge is performed, methods contains an object of name:mfa with type set to the factor used for that challenge. To see the results of a challenge, methods must be accessed in the next Action in the flow."

[SOLUTION]

Step 1: Action 1 (The Trigger)
In your first Action, you trigger the challenge and pause the pipeline:

exports.onExecutePostLogin = async (event, api) => {
  // Trigger step-up and PAUSE the pipeline
  api.authentication.challengeWithAny([
    { type: 'webauthn-platform' },
    { type: 'otp' },
    { type: 'push-notification' }
  ]);
};

Step 2: Action 2 (The Token Injection)
You must create a completely separate Action and place it after Action 1 in your Login Flow pipeline. When the pipeline resumes, this Action executes, and the type property will be populated:

exports.onExecutePostLogin = async (event, api) => {
  // Find the MFA method in the array
  const mfaMethod = event.authentication.methods.find(m => m.name === 'mfa');

  if (mfaMethod && mfaMethod.type) {
    // mfaMethod.type will be 'otp', 'webauthn-platform', etc.
    const completedFactor = mfaMethod.type;
    
    //Surface it to your application
    api.idToken.setCustomClaim('https://yourdomain.com/mfa_factor', completedFactor);
    api.accessToken.setCustomClaim('https://yourdomain.com/mfa_factor', completedFactor);
  }
};

Kind Regards,
Nik