apereo/cas · error · FailedLoginException

Unable to validate multifactor credential with status

Error message

Unable to validate multifactor credential with status 

What it means

RestfulCasSimpleMultifactorAuthenticationService.generate calls an external REST endpoint to validate the user's MFA credential and mint a simple-MFA token. If the endpoint responds with an unexpected/non-success statusCode (not the accepted success status with a token payload), it throws FailedLoginException "Unable to validate multifactor credential with status <statusCode>". The MFA decision was delegated to the REST service and it refused or errored.

Solutions

  1. Check the remote MFA REST service's response status and logs for why validation failed
  2. Verify cas.authn.mfa.rest (simple-mfa REST) endpoint URL, method, and auth credentials are correct
  3. Confirm the remote service returns the expected body/token format on success
  4. Retry after fixing the upstream service; treat 5xx as transient and re-attempt the MFA step
Defensive patterns

Strategy: retry

Validate before calling

// preflight the REST MFA endpoint availability and expected contract
var probe = HttpUtils.executeHttpRequestToServices(...mfaRestUrl, HttpMethod.GET, ...);
if (probe.getStatus() != 200) throw new IllegalStateException("MFA REST endpoint unhealthy: " + probe.getStatus());

Try / catch

try {
    return restMfaService.generate(principal, service, credential);
} catch (FailedLoginException e) {
    if (isServerError(statusCode)) { // 5xx: transient upstream issue
        return retryWithBackoff(() -> restMfaService.generate(principal, service, credential));
    }
    throw e; // 4xx: user's code genuinely rejected; prompt for a new code
}

Prevention

When it happens

Trigger: generate() when the HTTP response from the configured simple-mfa REST endpoint returns a status code outside the expected success range, or a success status without the expected token body — e.g. 401/403 wrong code, 404/500 endpoint problems.

Common situations: Remote MFA service rejecting the submitted code (wrong/expired code); wrong REST endpoint URL or credentials configured for the MFA REST service; the remote service returning an undocumented status or empty body on success; network/proxy layer returning 502/503.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-simple-mfa-core/src/main/java/org/apereo/cas/mfa/simple/validation/RestfulCasSimpleMultifactorAuthenticationService.java:90

                .entity(writer.toString())
                .basicAuthPassword(properties.getBasicAuthPassword())
                .basicAuthUsername(properties.getBasicAuthUsername())
                .maximumRetryAttempts(properties.getMaximumRetryAttempts())
                .parameters(parameters)
                .build();
            response = HttpUtils.execute(exec);
            val statusCode = response.getCode();
            if (HttpStatus.valueOf(statusCode).is2xxSuccessful()) {
                try (val content = ((HttpEntityContainer) response).getEntity().getContent()) {
                    val result = IOUtils.toString(content, StandardCharsets.UTF_8);
                    val mfaFactory = (CasSimpleMultifactorAuthenticationTicketFactory) ticketFactory.get(CasSimpleMultifactorAuthenticationTicket.class);
                    LOGGER.debug("Multifactor authentication token received is [{}]", result);
                    val token = mfaFactory.create(result, service, CollectionUtils.wrap(CasSimpleMultifactorAuthenticationConstants.PROPERTY_PRINCIPAL, principal));
                    LOGGER.debug("Created multifactor authentication token [{}] for service [{}]", token.getId(), service);
                    return token;
                }
            }
            throw new FailedLoginException("Unable to validate multifactor credential with status " + statusCode);
        } finally {
            HttpUtils.close(response);
        }
    }

    @Override
    public void store(final CasSimpleMultifactorAuthenticationTicket token) throws Exception {
        HttpResponse response = null;
        try (val writer = new StringWriter()) {
            MAPPER.writer().with(new MinimalPrettyPrinter()).writeValue(writer, token);

            val headers = CollectionUtils.<String, String>wrap(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            headers.putAll(properties.getHeaders());
            val exec = HttpExecutionRequest.builder()
                .method(HttpMethod.POST)
                .headers(headers)
                .url(properties.getUrl())
                .entity(writer.toString())

View on GitHub (pinned to e7288fc434)