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
- Wait for the rate-limit window to refill and retry with a fresh code
- Raise cas.authn.mfa.simple.core.rate-limit attempt count or shorten the refill period if too strict
- Have the user request a new MFA code rather than retrying a stale one
- 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
- Tune cas.authn.mfa.simple rate-limit attempts/window to realistic retry behavior
- Show users a lockout message with retry timing instead of a generic failure
- Alert on throttling spikes — they often indicate brute-force attempts
- Don't auto-retry aggressively; exponential backoff avoids compounding the throttle
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
- No registered devices for multifactor authentication could…
- Credential principal
- Unable to locate principal for token
- Principal assigned to token
- Throttled submission
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)