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

  1. Check the remote system and re-enable the user account
  2. Verify what actually returns 403: hit the endpoint manually with the same payload to rule out proxies/WAFs
  3. Confirm the REST endpoint URL and auth configuration in cas.authn.rest point at the intended service
  4. 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

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.

Related errors


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)