apereo/cas · error · FailedLoginException
Could not authenticate account for
Error message
Could not authenticate account for
What it means
SyncopeAuthenticationHandler.authenticateUsernamePasswordInternal throws FailedLoginException when authenticateSyncopeUser returns an empty Optional, i.e. Syncope did not authenticate the supplied username/password (bad credentials, unknown user, or an upstream call that yielded no user). CAS translates that into a generic FailedLoginException for the standard authentication flow.
Solutions
- Verify the username/password are correct and the user is in the configured Syncope domain (cas.authn.syncope.domain)
- Check cas.authn.syncope.url, basic-authn-username/password used to call Syncope, and network reachability from CAS to Syncope
- Confirm the user's realm in Syncope is one the configured admin account may access
- Enable debug logging on Syncope side to see why the auth REST call returns empty
- If this persists without user error, inspect Syncope access tokens / core REST config
Example fix
// before cas.authn.syncope.url=http://localhost:8080/syncope cas.authn.syncope.domain=Two // after cas.authn.syncope.url=http://syncope:8080/syncope cas.authn.syncope.domain=Two cas.authn.syncope.basic-authn-username=admin cas.authn.syncope.basic-authn-password=secret
Defensive patterns
Strategy: retry
Validate before calling
// before CAS login, check reachability
try (var resp = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create(syncopeUrl + "/rest/users?FIQLString=username==" + u)).header("Authorization", basic).build(),
BodyHandlers.ofString())) {
if (resp.statusCode() != 200) throw new IllegalStateException("Syncope unreachable: " + resp.statusCode());
} Try / catch
try {
return authenticationManager.authenticate(transaction);
} catch (FailedLoginException e) {
LOGGER.warn("Syncope login failed for user", e);
throw new BadCredentialsException("invalid.username.or.password");
} catch (Exception e) {
// network / 5xx — consider retry or degraded mode
} Prevention
- Double-check cas.authn.syncope.url and domain against Syncope deployment
- Use a dedicated Syncope admin account with only the needed realm rights
- Add health-check monitoring of Syncope from the CAS host
- Log (without secrets) the Syncope REST response status on failure for triage
- Don't leak detailed failure reasons to end users; show generic bad-credentials message
When it happens
Trigger: POST to Syncope's REST authentication endpoint for the configured domain returns no user (401/404, wrong credentials, or non-matching realm) so authenticateSyncopeUser yields Optional.empty.
Common situations: Wrong password or username typo; user exists in a different Syncope domain/realm than cas.authn.syncope.domain; misconfigured syncopeRestUrl or admin credentials for the authentication REST call; Syncope access-token/admin user lacking rights in the target realm.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- FailedLoginException
- Could not authenticate forbidden account for
- Account password must change for
- Invalid credentials:
- Unable to accept cookie for authentication
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/ab793cd50a0ab65f.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-syncope-authentication/src/main/java/org/apereo/cas/syncope/SyncopeAuthenticationHandler.java:75
val result = authenticateSyncopeUser(credential);
if (result.isPresent()) {
val user = result.get();
LOGGER.debug("Received Syncope user object as [{}]", user);
if (user.has("suspended") && user.get("suspended").asBoolean()) {
throw new AccountDisabledException(
"Could not authenticate forbidden account for " + credential.getUsername());
}
if (user.has("mustChangePassword") && user.get("mustChangePassword").asBoolean()) {
throw new AccountPasswordMustChangeException(
"Account password must change for " + credential.getUsername());
}
val principalAttributes = SyncopeUtils.convertFromUserEntity(user, properties.getAttributeMappings());
val name = properties.getAttributeMappings().getOrDefault("domain", "syncopeDomain");
principalAttributes.put(name, CollectionUtils.wrapList(syncopeDomain));
val principal = principalFactory.createPrincipal(user.get("username").asString(), principalAttributes);
return createHandlerResult(credential, principal, new ArrayList<>());
}
throw new FailedLoginException("Could not authenticate account for " + credential.getUsername());
}
protected Optional<JsonNode> authenticateSyncopeUser(final UsernamePasswordCredential credential) {
HttpResponse response = null;
try {
val syncopeRestUrl = Strings.CI.appendIfMissing(
SpringExpressionLanguageValueResolver.getInstance().resolve(properties.getUrl()),
"/rest/users/self");
val exec = HttpExecutionRequest.builder()
.method(HttpMethod.GET)
.url(syncopeRestUrl)
.basicAuthUsername(credential.getUsername())
.basicAuthPassword(credential.toPassword())
.headers(CollectionUtils.wrap(SyncopeUtils.SYNCOPE_HEADER_DOMAIN, syncopeDomain))
.maximumRetryAttempts(properties.getMaxRetryAttempts())
.build();
response = HttpUtils.execute(exec);
if (response != null) {View on GitHub (pinned to e7288fc434)