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

  1. Unlock the account in the remote system and retry
  2. Review the remote system's lockout policy/thresholds
  3. Verify no proxy/CDN is generating 423 responses unrelated to account state
  4. 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

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.

Related errors


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)