apereo/cas · error · FailedLoginException

Could not authenticate account for

Error message

Could not authenticate account for ${username}

What it means

RestAuthenticationHandler throws FailedLoginException when the remote REST authentication endpoint replies HTTP 401 UNAUTHORIZED. This means the credentials were rejected as invalid — the standard 'bad username or password' outcome for REST authentication.

Solutions

  1. Confirm the user's username/password are correct
  2. Check cas.authn.rest.password-encoder settings match what the endpoint expects (plain vs encoded)
  3. If the endpoint itself requires basic auth, verify the configured basicAuthUsername/basicAuthPassword are valid
  4. Inspect endpoint logs to see whether 401 came from user validation or endpoint authentication

Example fix

// before
POST /auth  ->  401 Unauthorized (wrong password)
// after
POST /auth  ->  200 OK (correct credentials)
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate credentials format client-side before the call
if (username == null || username.isBlank() || password == null || password.isEmpty()) {
    throw new IllegalArgumentException("Username and password required");
}

Type guard

boolean isUnauthorized(HttpResponse r) { return r != null && r.getCode() == 401; }

Try / catch

try {
    return restHandler.authenticate(credential);
} catch (FailedLoginException e) {
    LOGGER.debug("Bad credentials for REST auth: {}", e.getMessage());
    throw e; // let CAS throttling/warning machinery handle it
}

Prevention

When it happens

Trigger: authenticateUsernamePasswordInternal sends the encoded credentials to the REST endpoint; it responds 401 and the switch maps UNAUTHORIZED -> FailedLoginException.

Common situations: User typed a wrong password; client and server disagree on password encoding (e.g. the endpoint expects raw but CAS sends encoded); credentials for the endpoint's own basic-auth are wrong so 401 reflects the endpoint, not the user.

Understand the failure class

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/776f90c0cad192ca. Report an issue: GitHub.

Appendix: source

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

        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);
                LOGGER.debug("REST authentication response received: [{}]", result);

View on GitHub (pinned to e7288fc434)