apereo/cas · warning · FailedLoginException

Validation attempt for principal is throttled

Error message

Validation attempt for principal  is throttled

What it means

DefaultCasSimpleMultifactorAuthenticationService.validate consumes a token from a bucket4j rate-limit bucket keyed by the principal id before validating the MFA code. If the bucket has no tokens left (result.isConsumed() false), it throws FailedLoginException "Validation attempt for principal <id> is throttled". This is rate limiting on MFA code attempts, protecting against brute-force code guessing.

Solutions

  1. Wait for the rate-limit window to refill and retry with a fresh code
  2. Raise cas.authn.mfa.simple.core.rate-limit attempt count or shorten the refill period if too strict
  3. Have the user request a new MFA code rather than retrying a stale one
  4. Investigate repeated failures for the principal id as a possible credential-stuffing attempt
Defensive patterns

Strategy: retry

Try / catch

try {
    return mfaService.validate(resolvedPrincipal, credential);
} catch (FailedLoginException e) {
    if (e.getMessage() != null && e.getMessage().contains("is throttled")) {
        // back off until the bucket refills, then retry with a fresh code
        Thread.sleep(backoffMillis);
        return retryWithFreshCode(resolvedPrincipal);
    }
    throw e;
}

Prevention

When it happens

Trigger: validate() when bucketConsumer.consume(principalId) returns a result with isConsumed()==false — the user (or an attacker against their id) exhausted the configured MFA attempt rate limit within the refill window.

Common situations: Users repeatedly submitting wrong/expired codes; shared NAT or scripted clients hammering validation; rate-limit window configured too small (few attempts, long refill) causing legitimate lockouts; automated tests tripping the limiter.

Related errors


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

Appendix: source

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

    @Override
    public Principal fetch(final CasSimpleMultifactorTokenCredential tokenCredential) {
        return Optional.ofNullable(getMultifactorAuthenticationTicket(tokenCredential))
            .map(this::getPrincipalFromTicket)
            .orElse(null);
    }

    @Override
    public void update(final Principal principal, final Map<String, Object> attributes) {
        accountServiceProvider.ifAvailable(service -> service.update(principal, attributes));
    }

    @Override
    public Principal validate(final Principal resolvedPrincipal,
                              final CasSimpleMultifactorTokenCredential credential) throws Exception {
        val result = bucketConsumer.consume(resolvedPrincipal.getId());
        if (!result.isConsumed()) {
            throw new FailedLoginException("Validation attempt for principal " + resolvedPrincipal.getId() + " is throttled");
        }
        val acct = getMultifactorAuthenticationTicket(credential);
        LOGGER.debug("Received token [{}] and principal id [{}]", acct, resolvedPrincipal.getId());
        val principal = validateTokenForPrincipal(resolvedPrincipal, acct);
        deleteToken(acct);
        LOGGER.debug("Validated token [{}] successfully for [{}].", credential.getId(), resolvedPrincipal.getId());
        return principal;
    }
}

View on GitHub (pinned to e7288fc434)