apereo/cas · error · FailedLoginException
FailedLoginException
Error message
FailedLoginException
What it means
RedisAuthenticationHandler throws FailedLoginException (no-arg) when the account exists in Redis but the stored (hashed) password does not match the presented password per the configured PasswordEncoder. The account lookup succeeded; this is purely a credential mismatch.
Solutions
- Re-provision the account password encoded with the same algorithm configured in cas.authn.password.encoding.*
- Align the PasswordEncoder configuration between the provisioning writer and CAS
- Test the encoder manually: encoder.matches(raw, storedHash) to isolate algorithm vs data problems
- Reset the user's password through the normal flow
Example fix
// before: stored with different algorithm than CAS encoder redisTemplate.opsForValue().set(u, new RedisUserAccount(u, md5Hex(pw), Status.OK, attrs)); // after: encode with the same encoder CAS is configured with redisTemplate.opsForValue().set(u, new RedisUserAccount(u, new BCryptPasswordEncoder().encode(pw), Status.OK, attrs));
Defensive patterns
Strategy: validation
Validate before calling
RedisUserAccount acct = (RedisUserAccount) redisTemplate.opsForValue().get(username);
if (acct != null && !passwordEncoder.matches(rawPassword, acct.getPassword())) {
// fail fast client-side / prompt reset
} Try / catch
try {
authHandler.authenticate(credential);
} catch (FailedLoginException e) {
// generic bad-credentials message; do not leak which check failed
} Prevention
- Use one PasswordEncoder/algorithm for provisioning and CAS
- Store only single-pass encoded hashes
- Keep a smoke-test user per algorithm configuration
When it happens
Trigger: getPasswordEncoder().matches(originalPassword, account.getPassword()) returns false for an existing RedisUserAccount.
Common situations: PasswordEncoder misconfigured so hashes were written with a different algorithm than CAS verifies with (e.g. bcrypt stored but plain/pbkdf2 expected); password changed elsewhere; provisioning stored the cleartext or double-hashed password.
Related errors
- AccountNotFoundException
- AccountDisabledException
- AccountExpiredException
- Could not authenticate account for
- Could not authenticate provided credentials
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/75cd0a061747faa9.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-redis-authentication/src/main/java/org/apereo/cas/redis/RedisAuthenticationHandler.java:41
public RedisAuthenticationHandler(final String name,
final PrincipalFactory principalFactory, final Integer order,
final CasRedisTemplate redisTemplate) {
super(name, principalFactory, order);
this.redisTemplate = redisTemplate;
}
@Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(
final UsernamePasswordCredential credential,
final String originalPassword) throws Throwable {
val account = (RedisUserAccount) redisTemplate.opsForValue().get(credential.getUsername());
if (account == null) {
throw new AccountNotFoundException();
}
if (!getPasswordEncoder().matches(originalPassword, account.getPassword())) {
LOGGER.warn("Account password on record for [{}] does not match the given/encoded password", credential.getId());
throw new FailedLoginException();
}
switch (account.getStatus()) {
case DISABLED -> throw new AccountDisabledException();
case EXPIRED -> throw new AccountExpiredException();
case LOCKED -> throw new AccountLockedException();
case MUST_CHANGE_PASSWORD -> throw new AccountPasswordMustChangeException();
case OK -> LOGGER.debug("Account status is OK");
}
val principal = principalFactory.createPrincipal(account.getUsername(), account.getAttributes());
return createHandlerResult(credential, principal, new ArrayList<>());
}
}
View on GitHub (pinned to e7288fc434)