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)
- 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);
}
};
- 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.
- 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:
- 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");
}
};
- In your onboarding flow, render the
mfa-webauthn-platform-enrollment screen to allow the user to enroll their MFA passkey.
- 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:
- 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;
};
- Auth0 will recognize the user’s existing session (from the login passkey) and prompt only for MFA, not for re-login.
- 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:
- Opening a feature request with Auth0 Support or via the Product Feedback Category on the Auth0 Community to express demand for unified passkey credentials.
- Considering alternative MFA methods (SMS, email, OTP) for step-up authentication if passkey duplication is unacceptable.
- Monitoring Auth0’s product updates for future WebAuthn enhancements.
Kind Regards,
Nik