apereo/cas · error · AccountDisabledException
Could not authenticate forbidden account for
Error message
Could not authenticate forbidden account for ${username} What it means
RestAuthenticationHandler throws AccountDisabledException when the remote REST authentication endpoint replies HTTP 403 FORBIDDEN. CAS interprets 403 as a disabled account rather than a bad password, and the username is embedded in the message for troubleshooting.
Solutions
- Check the remote system and re-enable the user account
- Verify what actually returns 403: hit the endpoint manually with the same payload to rule out proxies/WAFs
- Confirm the REST endpoint URL and auth configuration in cas.authn.rest point at the intended service
- If your endpoint uses 403 for generic denial, adjust the endpoint or a custom handler mapping
Example fix
// before: endpoint returns 403 for deactivated user HTTP/1.1 403 Forbidden // after re-enabling the account HTTP/1.1 200 OK
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the endpoint before user auth
curl -s -o /dev/null -w "%{http_code}" $CAS_REST_AUTH_URL # expect non-403 for a healthy route Type guard
boolean isForbidden(HttpResponse r) { return r != null && r.getCode() == 403; } Try / catch
try {
return restHandler.authenticate(credential);
} catch (AccountDisabledException e) {
LOGGER.warn("Disabled account or blocked request: {}", e.getMessage());
throw e;
} Prevention
- Keep remote accounts' enabled/disabled state in sync
- Test the endpoint manually with curl before blaming the user
- Ensure no WAF/proxy returns 403 for reasons unrelated to account state
- Document the 403 semantics contract with the endpoint team
When it happens
Trigger: authenticateUsernamePasswordInternal posts credentials to the configured REST endpoint via HttpUtils; the endpoint responds with status 403 and the switch maps FORBIDDEN -> AccountDisabledException.
Common situations: The endpoint's user account was deactivated; a firewall/WAF or proxy returns 403 for blocked requests, misread as a disabled account; the remote service disabled the user after policy violations.
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
- e.getMessage()
- Unable to accept response status
- No credentials can be extracted to authenticate the REST…
- Unable to extract credentials for multifactor authentication
- AccountDisabledException
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/a9725fd5b008b588.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java:93
final UsernamePasswordCredential credential,
final String originalPassword) throws Throwable {
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);View on GitHub (pinned to e7288fc434)