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
- Read the status code in the message and check the endpoint's logs for that response
- Fix the endpoint/outage producing the unexpected status (5xx, 502/503 gateway issues)
- Verify cas.authn.rest.url points directly at the authentication endpoint with no redirecting proxy
- 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
- Add endpoint health checks and alerting for 5xx responses
- Point cas.authn.rest.url directly at the auth endpoint, avoiding redirecting gateways
- Map expected-but-unhandled codes (429, 302) on the endpoint side
- Record the status code from the message to speed triage
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
- Unable to accept response status
- No credentials can be extracted to authenticate the REST…
- Unable to extract credentials for multifactor authentication
- Could not authenticate forbidden account for
- Could not authenticate account for
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)