apereo/cas · error · AccountExpiredException

Could not authenticate expired account for

Error message

Could not authenticate expired account for ${username}

What it means

RestAuthenticationHandler throws AccountExpiredException when the remote REST endpoint replies HTTP 412 PRECONDITION_FAILED. CAS maps this status to an expired account, meaning the account (or its password) is no longer valid by time-based policy.

Solutions

  1. Renew/extend the account or password validity in the remote system
  2. Have the user reset an expired password
  3. If the endpoint uses 412 for other preconditions, change the endpoint semantics or write a custom handler mapping
  4. Synchronize password-expiry policies between CAS and the remote store

Example fix

// before: remote account
{"user":"jdoe","expires":"2024-01-01"}
// after extension
{"user":"jdoe","expires":"2027-01-01"}
Defensive patterns

Strategy: try-catch

Validate before calling

val acct = remoteUserStore.lookup(username);
if (acct != null && acct.getExpiresOn().isBefore(LocalDate.now())) {
    throw new AccountExpiredException("Account expired");
}

Type guard

boolean isPreconditionFailed(HttpResponse r) { return r != null && r.getCode() == 412; }

Try / catch

try {
    return restHandler.authenticate(credential);
} catch (AccountExpiredException e) {
    LOGGER.info("Expired account: {}", e.getMessage());
    throw e; // route to renewal/password-reset flow
}

Prevention

When it happens

Trigger: authenticateUsernamePasswordInternal receives HTTP 412 from the endpoint; the switch maps PRECONDITION_FAILED -> AccountExpiredException.

Common situations: Password aged out on the remote system; account validity/expiry date passed; the endpoint uses 412 for conditional-request failures unrelated to expiry, which CAS then misinterprets.

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/79252d812e2d5002. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java:97

        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());
                return createHandlerResult(credential, principal, getWarnings(response));

View on GitHub (pinned to e7288fc434)