Can login passkey and MFA passkey be merged into one credential?

What we want to achieve

We’re building a custom Universal Login (ACUL) with React. We need passkeys in two scenarios:

  1. Login passkey — Users authenticate directly with a passkey on the login page (Discoverable Credential)
  2. MFA passkey — For step-up authentication on sensitive operations, users verify via MFA WebAuthn Platform (Non-Discoverable Credential, enrolled on the mfa-webauthn-platform-enrollment screen)

The problem

These are two separate credentials. Users must register a passkey twice — once for login, once for MFA. This creates two issues:

  1. Double registration feels redundant — The user just authenticated with their fingerprint to log in, then is immediately asked to “Create Passkey” again for MFA enrollment.
  2. Duplicate entries in the OS passkey picker — Both credentials appear under the same email with no way to distinguish which is for login vs MFA (see screenshot below).

What we’ve already considered

  • max_age=0 for step-up: Forces full re-authentication, but this redirects the user back to the login page — unacceptable UX for in-app step-up.
  • Post-Login Action to skip MFA after passkey login: We know we can check event.authentication.methods for "passkey" and call api.multifactor.enable("none"). This solves the login flow, but doesn’t help with step-up — we still need MFA for sensitive operations.
  • We understand the technical difference: Login passkeys are Discoverable Credentials; MFA WebAuthn Platform credentials are Non-Discoverable. Auth0 treats them as separate systems.

Our question

  1. Is there any way to unify these into a single passkey credential that works for both login and MFA step-up?
  2. If not, is there a recommended approach for step-up authentication that avoids requiring a second passkey registration, or distinguish the mfa and login passkeys for a same user?
  3. Any plans on the Auth0 roadmap to address this?

Authentication Profile: Identifier First
Auth0 SDK: @auth0/auth0-acul-react v1.0.0

Hi @robert.huo

I will need some extra time researching this specific issue that you are experiencing regarding the passkeys implementation within your React ACUL screens.

Kind Regards,
Nik

Hi @robert.huo

I am sorry for the delayed reply to your inquiry, however, I needed additional time to investigate the matter in order to provide information which is as accurate as possible.

I can see that you are having issues in building a custom Auth0 Universal Login (ACUL) with React that requires passkeys for both initial login (Discoverable Credential) and MFA step-up (Non-Discoverable Credential / WebAuthn Platform). Users are currently forced to register two separate passkey credentials with no way to distinguish them in the OS passkey picker, creating redundant registration flow and confusing UX.

Auth0 does not currently support unifying a single passkey credential for both login and MFA step-up. Login passkeys (Discoverable Credentials) and MFA WebAuthn Platform credentials (Non-Discoverable Credentials) are separate authentication systems within Auth0 and cannot be merged into a single credential. However, there are practical approaches to improve the UX and reduce registration friction.

[ROOT CAUSE]
Auth0’s authentication architecture treats passkeys differently depending on their use case:

  • Login passkeys are Discoverable Credentials, which means they can be invoked by the user without specifying an account beforehand (the OS passkey picker shows them by email).
  • MFA WebAuthn Platform credentials are Non-Discoverable Credentials, which require account identification before the authentication challenge is presented.

These credential types are fundamentally different in the WebAuthn specification and cannot be unified into a single credential at the Auth0 platform level. Additionally, Auth0 stores them in separate credential systems: login passkeys are managed by Auth0’s primary authentication system, while MFA WebAuthn Platform credentials are managed by Auth0’s MFA system.

[Official Recommendation]

Option 1: Skip MFA enrollment immediately after passkey login (Best for typical UX)

  1. Use a Post-Login Action to detect when a user has authenticated with a passkey and skip MFA enrollment:
exports.onExecutePostLogin = async (event, api) => {
  // Check if user authenticated with passkey
  if (event.authentication.methods.includes("passkey")) {
    // Skip MFA for this session
    api.multifactor.enable("none");
    // Optionally: Mark user as having passkey for later step-up
    api.user.setAppMetadata("has_login_passkey", true);
  }
};
  1. For step-up authentication on sensitive operations, implement a custom step-up flow in your application:
    • When a sensitive operation requires MFA, do NOT redirect to Auth0’s MFA page.
    • Instead, use the ACUL WebAuthn Platform enrollment screen (mfa-webauthn-platform-enrollment) to allow the user to enroll an MFA credential on-demand, in-context.
    • After enrollment, verify the credential locally or use a separate MFA verification flow.
  2. Benefits: Users authenticate once with their login passkey, are not prompted

Option 2: Consolidate MFA registration into onboarding (For applications with onboarding flows)

If your application has a dedicated onboarding or account setup flow, enroll the MFA passkey immediately after the first login, before the user accesses the main dashboard:

  1. After passkey login, check if the user has an MFA credential enrolled:
exports.onExecutePostLogin = async (event, api) => {
  const hasMfaEnrolled = event.user.multifactor && event.user.multifactor.length > 0;

  if (event.authentication.methods.includes("passkey") && !hasMfaEnrolled) {
    // User logged in with passkey but has no MFA enrolled
    // Redirect to onboarding flow to enroll MFA
    api.redirect.sendUserTo("https://yourapp.com/onboarding/mfa");
  }
};
  1. In your onboarding flow, render the mfa-webauthn-platform-enrollment screen to allow the user to enroll their MFA passkey.
  2. After enrollment, redirect to the main dashboard.

Benefits: MFA enrollment happens once, in a dedicated flow, reducing friction. Users understand they are setting up a separate security credential for sensitive operations.

Option 3: Use step-up authentication with passkey challenge (Recommended for sensitive operations)

For in-app step-up authentication without requiring a second passkey enrollment:

  1. When a user attempts a sensitive operation, trigger step-up authentication using the /authorize endpoint with acr_values=http://schemas.openid.net/pape/policies/2007/06/multi-factor:
const handleSensitiveOperation = async () => {
  const auth0Domain = "YOUR_AUTH0_DOMAIN";
  const clientId = "YOUR_CLIENT_ID";
  const redirectUri = "https://yourapp.com/sensitive-operation-callback";

  const stepUpUrl = `https://${auth0Domain}/authorize?` +
    `client_id=${clientId}&` +
    `response_type=code&` +
    `redirect_uri=${encodeURIComponent(redirectUri)}&` +
    `scope=openid profile&` +
    `acr_values=http://schemas.openid.net/pape/policies/2007/06/multi-factor`;

  window.location.href = stepUpUrl;
};
  1. Auth0 will recognize the user’s existing session (from the login passkey) and prompt only for MFA, not for re-login.
  2. After MFA is completed, Auth0 redirects back to your application with new tokens that include "mfa" in the amr (Authentication Methods Reference) claim.

Benefits: No second passkey enrollment required. Users only authenticate with MFA when accessing sensitive operations. The existing passkey session is preserved.

Addressing the duplicate passkey picker issue:

Unfortunately, there is no way to prevent duplicate passkey entries in the OS passkey picker if the user has both a login passkey and an MFA passkey. This is a limitation of the WebAuthn specification and the OS-level passkey management system. However, you can mitigate this by:

  • Educating users during enrollment that they will see two passkey entries (one for login, one for sensitive operations).
  • Using descriptive names during enrollment to help users distinguish between them (e.g., “Login Passkey” vs. “Security Passkey”).
  • Implementing Option 1 or 2 above to delay MFA passkey enrollment until it is actually needed, reducing the likelihood that users will have both credentials enrolled simultaneously.

This is a known limitation in the passkey ecosystem. If this is a critical requirement for your use case, we recommend:

  1. Opening a feature request with Auth0 Support or via the Product Feedback Category on the Auth0 Community to express demand for unified passkey credentials.
  2. Considering alternative MFA methods (SMS, email, OTP) for step-up authentication if passkey duplication is unacceptable.
  3. Monitoring Auth0’s product updates for future WebAuthn enhancements.

Kind Regards,
Nik

Hi Nik, thank you for your reply and solutions, this is a clear guidance for us, while we are interested in some solutions you mentioned above:

  1. For Option 3, may I ask which MFA method will auth0 use? Will it still be Passkey? Or SMS? Or it depends on the tenant configuration?
  2. For the “Using descriptive names during enrollment to help users distinguish between them (e.g., “Login Passkey” vs. “Security Passkey”).”, may I ask where can I add this “descriptive names”? In which ACUL page? And will the description be shown in the OS passkey picker for user to have a distinguish to pick up the login passkey? Thx