apereo/cas · warning

No registered devices for multifactor authentication could…

Error message

No registered devices for multifactor authentication could be found for [{}]

What it means

After contact info is validated, SendPasswordResetInstructionsAction checks whether password reset requires MFA and, if so, whether the principal has any registered MFA devices. With no registered devices it logs this warn, adds the screen.mfaDenied message, and returns the 'deny' transition so no reset link is issued.

Solutions

  1. Have the user enroll an MFA device via the normal MFA registration flow first
  2. Relax cas.authn.pm.reset mfa requirements (e.g. remove required provider) if MFA should not gate resets
  3. Verify the MFA device registry/storage (e.g. mongo, json, redis) is configured and contains the user's records
  4. Confirm the principal resolution produces the same principal id used when devices were registered

Example fix

// before
cas.authn.pm.reset.mfa-enabled=true
// user has no registered devices -> deny
// after — either enroll the user, or:
cas.authn.pm.reset.mfa-enabled=false
Defensive patterns

Strategy: fallback

Validate before calling

boolean mfaRequired = casProperties.getAuthn().getPm().getReset().isMfaEnabled();
boolean hasDevices = mfaDeviceRegistry.findDevices(principalId).size() > 0;
if (mfaRequired && !hasDevices) {
    // guide user to MFA enrollment before requesting a password reset
}

Type guard

boolean hasRegisteredDevices(String principalId, MfaDeviceRegistry r) {
    return !r.findDevices(principalId).isEmpty();
}

Prevention

When it happens

Trigger: doesPasswordResetRequireMultifactorAuthentication(requestContext) is true (per cas.authn.pm.reset.mfa or registered-service policy) while hasPrincipalRegisteredMultifactorAuthenticationDevice finds zero device registrations for the user's principal.

Common situations: MFA required for PM reset but user never enrolled (no WebAuthn/Google Authenticator/TOTP records); principal attribute used to locate devices is missing; MFA provider's account registry store is empty or misconnected; policy recently changed to require MFA for existing users.

Understand the failure class

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/41b85bd38239add7. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-pm-webflow/src/main/java/org/apereo/cas/pm/web/flow/actions/SendPasswordResetInstructionsAction.java:132

            return getErrorEvent("contact.failed", "Unable to send email as no mail sender is defined", requestContext);
        }

        val query = buildPasswordManagementQuery(requestContext);
        if (StringUtils.isBlank(query.getUsername())) {
            return getErrorEvent("username.required", "No username is provided", requestContext);
        }

        val emails = locatePasswordResetRequestEmail(requestContext, query);
        val phones = locatePasswordResetRequestPhone(requestContext, query);

        if (emails.isEmpty() && phones.isEmpty()) {
            LOGGER.warn("No recipient is provided with a valid email/phone");
            return getInvalidContactEvent(requestContext);
        }
        WebUtils.putPasswordManagementQuery(requestContext, query);
        if (doesPasswordResetRequireMultifactorAuthentication(requestContext)
            && !hasPrincipalRegisteredMultifactorAuthenticationDevice(requestContext)) {
            LOGGER.warn("No registered devices for multifactor authentication could be found for [{}]", query.getUsername());
            WebUtils.addErrorMessageToContext(requestContext, "screen.mfaDenied.message");
            return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_DENY);
        }
        val service = WebUtils.getService(requestContext);
        val url = buildPasswordResetUrl(query.getUsername(), service);
        if (url != null) {
            val sendEmail = sendPasswordResetEmailToAccount(query.getUsername(), emails, url, requestContext);
            val sendSms = sendPasswordResetSmsToAccount(requestContext, phones, url);
            if (sendEmail.isSuccess() || sendSms) {
                return success(url);
            }
        } else {
            LOGGER.error("No password reset URL could be built and sent to [{}]", emails);
        }
        LOGGER.error("Failed to notify account [{}]", emails);
        return getErrorEvent("contact.failed", "Failed to send the password reset link via email address or phone", requestContext);
    }

View on GitHub (pinned to e7288fc434)