Users can enroll up to 10 MFA factors in Supabase Auth. When this limit is reached, attempting to enroll another factor fails with the 'too_many_enrolled_mfa_factors' error. This commonly happens when users cancel enrollment flows, leaving unverified factors that count toward the limit.
This error occurs when a Supabase Auth user has reached the maximum limit of 10 enrolled MFA (Multi-Factor Authentication) factors. Supabase enforces this limit to prevent abuse and manage authentication factor storage. The error is triggered by the auth service when the user attempts to enroll an additional factor beyond the 10-factor cap. A critical issue is that unverified factors (from incomplete enrollment flows) also count toward this limit, potentially blocking users from ever completing MFA setup if they cancel the enrollment process multiple times.
Use the MFA List Factors API to see all factors currently enrolled:
const { data, error } = await supabase.auth.mfa.listFactors();
if (error) {
console.error('Error listing factors:', error);
} else {
console.log('Enrolled factors:', data.factors);
data.factors.forEach((factor) => {
console.log(`- ${factor.factor_type} (verified: ${factor.verified})`);
});
}Note the status of each factor, particularly any unverified factors.
Look for any factors with verified: false in the list above. These incomplete enrollments are the most common cause. Remove them using the unenroll API:
const { error } = await supabase.auth.mfa.unenroll({
factorId: 'UNVERIFIED_FACTOR_ID',
});
if (error) {
console.error('Error unenrolling factor:', error);
} else {
console.log('Unverified factor removed successfully');
}Repeat for each unverified factor until you have fewer than 10 total factors.
If you still need to make room, consider removing backup/secondary MFA factors. For example, if you have multiple TOTP factors, you might keep just one verified TOTP and remove others:
const { data } = await supabase.auth.mfa.listFactors();
const backupTotpFactors = data.factors.filter(
(f) => f.factor_type === 'totp' && f.verified
);
// Keep the first one, remove others
for (let i = 1; i < backupTotpFactors.length; i++) {
const { error } = await supabase.auth.mfa.unenroll({
factorId: backupTotpFactors[i].id,
});
if (!error) {
console.log('Removed backup factor');
}
}Once you have fewer than 10 factors and removed unverified ones, retry the enrollment:
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp', // or 'phone' for SMS-based MFA
});
if (error) {
console.error('Enrollment failed:', error);
} else {
console.log('Factor enrolled:', data);
// Complete verification with the challenge/verify APIs
}To avoid this issue recurring:
1. Complete the enrollment flow: Always finish the challenge and verify steps. Don't cancel mid-flow.
2. Clean up on app startup: Check for unverified factors and remove them:
async function cleanupUnverifiedFactors() {
const { data } = await supabase.auth.mfa.listFactors();
for (const factor of data.factors) {
if (!factor.verified) {
await supabase.auth.mfa.unenroll({ factorId: factor.id });
}
}
}3. Handle enrollment errors gracefully: Catch the 'too_many_enrolled_mfa_factors' error and show users which factors they can remove.
Supabase Auth has a hard limit of 10 MFA factors per user. Both verified and unverified factors count toward this limit. This can create a trap where if a user cancels enrollment 10 times without completing any, they become permanently locked out of MFA setup. The Supabase team has discussed adding automatic cleanup of unverified factors (see auth#1371), but until then, applications should implement client-side cleanup or handle the error gracefully. For administrative cleanup, use the Supabase admin API (supabase.auth.admin.mfa.deleteFactor()) if you have backend access. Note that deleting a verified factor logs the user out of all active sessions.
email_address_not_authorized: Email sending to this address is not authorized
Email address not authorized for sending in Supabase Auth
reauthentication_needed: Reauthentication required for security-sensitive actions
Reauthentication required for security-sensitive actions
no_authorization: No authorization header was provided
How to fix "no authorization header was provided" in Supabase
otp_expired: OTP has expired
How to fix 'otp_expired: OTP has expired' in Supabase
bad_oauth_state: OAuth state parameter is missing or invalid
How to fix 'bad_oauth_state: OAuth state parameter missing' in Supabase