apereo/cas · error · AccountLockedException
Could not authenticate locked account for
Error message
Could not authenticate locked account for ${username} What it means
RestAuthenticationHandler throws AccountLockedException when the remote REST authentication endpoint replies HTTP 423 LOCKED. CAS interprets this as the account being administratively locked on the remote side, rejecting authentication even with valid credentials.
Solutions
- Unlock the account in the remote system and retry
- Review the remote system's lockout policy/thresholds
- Verify no proxy/CDN is generating 423 responses unrelated to account state
- Coordinate CAS throttling settings with the remote lockout so both systems agree
Example fix
// before: remote store
{"user":"jdoe","locked":true}
// after unlock
{"user":"jdoe","locked":false} Defensive patterns
Strategy: try-catch
Validate before calling
val acct = remoteUserStore.lookup(username);
if (acct != null && acct.isLocked()) {
throw new AccountLockedException("Account locked upstream");
} Type guard
boolean isLocked(HttpResponse r) { return r != null && r.getCode() == 423; } Try / catch
try {
return restHandler.authenticate(credential);
} catch (AccountLockedException e) {
LOGGER.warn("Locked account: {}", e.getMessage());
throw e; // route to unlock workflow
} Prevention
- Align lockout thresholds between CAS and the remote system
- Provide a self-service or admin unlock path
- Monitor 423 rates to detect brute-force or policy mismatch
- Verify no middleware fabricates 423 responses
When it happens
Trigger: authenticateUsernamePasswordInternal posts credentials; the endpoint responds 423 LOCKED and the switch maps LOCKED -> AccountLockedException.
Common situations: Remote system locked the account after repeated failed attempts; admin manually locked the user; a middlebox returns 423 for unrelated reasons and CAS misreads it as account lockout.
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unable to accept response status
- No credentials can be extracted to authenticate the REST…
- Unable to extract credentials for multifactor authentication
- AccountLockedException
- Could not authenticate forbidden account for
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/c992153e4b7f8e56.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java:96
var response = (HttpResponse) null;
try {
val exec = HttpExecutionRequest
.builder()
.basicAuthUsername(credential.getUsername())
.basicAuthPassword(credential.toPassword())
.method(HttpMethod.valueOf(properties.getMethod().toUpperCase(Locale.ENGLISH)))
.url(SpringExpressionLanguageValueResolver.getInstance().resolve(properties.getUri()))
.httpClient(httpClient)
.build()
.withoutRetry();
response = HttpUtils.execute(exec);
val status = HttpStatus.resolve(Objects.requireNonNull(response).getCode());
return switch (Objects.requireNonNull(status)) {
case OK -> buildPrincipalFromResponse(credential, response);
case FORBIDDEN -> throw new AccountDisabledException("Could not authenticate forbidden account for " + credential.getUsername());
case UNAUTHORIZED -> throw new FailedLoginException("Could not authenticate account for " + credential.getUsername());
case NOT_FOUND -> throw new AccountNotFoundException("Could not locate account for " + credential.getUsername());
case LOCKED -> throw new AccountLockedException("Could not authenticate locked account for " + credential.getUsername());
case PRECONDITION_FAILED -> throw new AccountExpiredException("Could not authenticate expired account for " + credential.getUsername());
case PRECONDITION_REQUIRED -> throw new AccountPasswordMustChangeException("Account password must change for " + credential.getUsername());
default -> throw new FailedLoginException("Rest endpoint returned an unknown status code " + status + " for " + credential.getUsername());
};
} finally {
HttpUtils.close(response);
}
}
protected AuthenticationHandlerExecutionResult buildPrincipalFromResponse(
final UsernamePasswordCredential credential,
final HttpResponse response) throws Throwable {
try {
try (val content = ((HttpEntityContainer) response).getEntity().getContent()) {
val result = IOUtils.toString(content, StandardCharsets.UTF_8);
LOGGER.debug("REST authentication response received: [{}]", result);
val principalFromRest = MAPPER.readValue(result, Principal.class);
val principal = principalFactory.createPrincipal(principalFromRest.getId(), principalFromRest.getAttributes());View on GitHub (pinned to e7288fc434)