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

  1. Re-provision the account password encoded with the same algorithm configured in cas.authn.password.encoding.*
  2. Align the PasswordEncoder configuration between the provisioning writer and CAS
  3. Test the encoder manually: encoder.matches(raw, storedHash) to isolate algorithm vs data problems
  4. 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

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


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)