apereo/cas · error · FailedLoginException

Rest endpoint returned an unknown status code

Error message

Rest endpoint returned an unknown status code ${status} for ${username}

What it means

RestAuthenticationHandler throws FailedLoginException with the message 'Rest endpoint returned an unknown status code ...' when the endpoint replies with any HTTP status not explicitly handled by the switch (not 200/401/403/404/423/412/428) or a status that cannot be resolved. CAS fails safe: unrecognized outcomes are treated as failed logins with the raw status recorded in the message.

Solutions

  1. Read the status code in the message and check the endpoint's logs for that response
  2. Fix the endpoint/outage producing the unexpected status (5xx, 502/503 gateway issues)
  3. Verify cas.authn.rest.url points directly at the authentication endpoint with no redirecting proxy
  4. Handle known-but-unmapped codes (e.g. 429 rate limiting) on the endpoint side or via a custom handler

Example fix

// before
curl https://api.example.com/auth  ->  502 Bad Gateway
// after fixing upstream service
curl https://api.example.com/auth  ->  200 OK
Defensive patterns

Strategy: retry

Validate before calling

// Health-check the endpoint before routing auth traffic
var code = new URL(restAuthUrl).openConnection().connect(); // verify reachable, no redirects/5xx

Type guard

boolean isHandledStatus(HttpResponse r) {
    return r != null && Set.of(200, 401, 403, 404, 412, 423, 428)
        .contains(r.getCode());
}

Try / catch

try {
    return restHandler.authenticate(credential);
} catch (FailedLoginException e) {
    if (e.getMessage().contains("unknown status code")) {
        LOGGER.error("REST auth endpoint unhealthy: {}", e.getMessage());
        // page ops / consider circuit breaker before failing user login
    }
    throw e;
}

Prevention

When it happens

Trigger: authenticateUsernamePasswordInternal receives a status like 500, 502, 503, 429, 301, or an unresolvable code from the REST endpoint, falling into the default branch.

Common situations: The endpoint service is crashing or misconfigured (5xx); a gateway/load balancer intercepts requests (502/503); rate limiting returns 429; a redirect (3xx) from a mis-URL'd endpoint; TLS or proxy layers returning unexpected codes.

Related errors


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

Appendix: source

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

                .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));
            }
        } catch (final Throwable e) {

View on GitHub (pinned to e7288fc434)