Combination of Authentication profile with identifier first and biometrics and webauthn-platform as MFA

We have enabled our authentication profile with Identifier first and biometrics option.

And we also have written custom post-login triggers for MFA where we are challenging the user with webauthn-platform if they’ve enrolled with it.

const enrolledFactors = event.user.enrolledFactors || [];
const enrolledTypes = new Set(enrolledFactors.map((factor) => factor.type));

const hasWebAuthnPlatform = enrolledTypes.has('webauthn-platform');
const hasOtp = enrolledTypes.has('otp');
const hasPhone = enrolledTypes.has('phone');

if (hasWebAuthnPlatform || hasOtp || hasPhone) {
    const preferredFactor = hasWebAuthnPlatform
      ? { type: 'webauthn-platform' }
      : hasOtp
        ? { type: 'otp' }
        : { type: 'phone', options: { preferredMethod: 'sms' } };

    api.authentication.challengeWith(preferredFactor, {
      additionalFactors: enrolledFactors
        .filter((factor) => factor.type !== preferredFactor.type)
        .map((factor) => ({ type: factor.type }))
    });

When Auth0 login recognizes the webauthn cookie in the browser, by default they’re prompted to login using biometrics and if user for some reason can’t login this way and choose to proceed with password, by default our post-login trigger is showing them the webauthN MFA immediately. Is there a way using event object to know that user skipped biometric option in the first attempt so we can challenge with another factor in our trigger?

Hi @karuissobusy

You are asking whether the post-login Action can detect that a user declined the biometric prompt in Identifier First + Biometrics and signed in with a password instead, so the Action can challenge with a different factor than WebAuthn.

Solution: The post-login event includes an event.authentication.methods array that records how the user authenticated in the current session. Use it to distinguish a password sign-in from a biometric sign-in. Confirm the exact method names in your tenant before deploying the final logic.

  1. Deploy a temporary post-login Action that logs JSON.stringify(event.authentication) to the Action logs, and deploy it to a non-production tenant.
  2. Sign in once using biometrics and once by choosing password on the Universal Login screen, then record the name values for each path.
  3. Replace the challenge logic in your Action with the version below, adjusting the method names to match what you recorded in step 2.
  4. Test both paths. A biometric sign-in should not trigger an extra challenge, and a password sign-in should trigger the OTP or phone challenge.
exports.onExecutePostLogin = async (event, api) => {
  const enrolledFactors = event.user.enrolledFactors || [];
  const enrolledTypes = new Set(enrolledFactors.map((f) => f.type));
  const methods = (event.authentication?.methods || []).map((m) => m.name);

  // Adjust these names after verifying them in the Action logs (step 2).
  const usedWebAuthn = methods.some((m) => m.startsWith('webauthn'));
  const usedPassword = methods.includes('pwd');

  if (usedWebAuthn) {
    return; // The user already verified with biometrics in this session.
  }

  const hasWebAuthnPlatform = enrolledTypes.has('webauthn-platform');
  const hasOtp = enrolledTypes.has('otp');
  const hasPhone = enrolledTypes.has('phone');

  let preferredFactor = null;
  if (usedPassword && (hasOtp || hasPhone)) {
    preferredFactor = hasOtp
      ? { type: 'otp' }
      : { type: 'phone', options: { preferredMethod: 'sms' } };
  } else if (hasWebAuthnPlatform) {
    preferredFactor = { type: 'webauthn-platform' };
  } else if (hasOtp) {
    preferredFactor = { type: 'otp' };
  } else if (hasPhone) {
    preferredFactor = { type: 'phone', options: { preferredMethod: 'sms' } };
  }

  if (preferredFactor) {
    api.authentication.challengeWith(preferredFactor, {
      additionalFactors: enrolledFactors
        .filter((f) => f.type !== preferredFactor.type)
        .map((f) => ({ type: f.type })),
    });
  }
};

Important caveat: Identifier First + Biometrics controls the Universal Login screen. The biometric prompt and the password fallback are shown by Auth0 before the Action runs, and the Action cannot remove or reorder that screen. If you need the choice screen removed, the community recommends Identifier First + Passkeys as the replacement profile for this flow.

Please follow up if you need further assistance after checking the logged method names.

Kind Regards,
Nik