apereo/cas · error · FailedLoginException

Password does not match value on record.

Error message

Password does not match value on record.

What it means

CAS's QueryAndEncodeDatabaseAuthenticationHandler throws FailedLoginException('Password does not match value on record.') when the password returned by the configured SQL query, after digesting the submitted password, does not equal the stored value. It is the handler's explicit 'wrong password' branch in authenticateUsernamePasswordInternal.

Solutions

  1. Verify the configured passwordEncoder type, encoding and characterEncoding match how passwords were stored in the database
  2. Confirm the saltFieldName column exists in the query results and holds the salt used at storage time
  3. Compare a known digest of the test password against the stored value manually (SELECT ... WHERE username=...)
  4. Check the SQL fieldPassword/passwordFieldName property points at the actual password column

Example fix

// before: schema stores SHA-512 hex but config uses default
// cas.authn.jdbc.encode[0].passwordEncoder=DEFAULT
// after
// cas.authn.jdbc.encode[0].passwordEncoder=SHA-512
// cas.authn.jdbc.encode[0].passwordEncoderCharsetName=UTF-8
Defensive patterns

Strategy: validation

Validate before calling

// Before deploy, verify digesting matches storage for a known user
String stored = jdbc.queryForObject("SELECT " + passwordFieldName + " FROM users WHERE username=?", String.class, user);
String encoded = passwordEncoder.encode(rawPassword, Map.of(saltFieldName, saltFromDb));
if (!stored.equalsIgnoreCase(encoded)) throw new IllegalStateException("Encoder/schema mismatch for " + user);

Try / catch

try {
    authResult = authenticationHandler.authenticate(credential);
} catch (FailedLoginException e) {
    // treat as bad credentials; do NOT leak encoder details to the user
    audit.recordFailure(user, e.getMessage());
    throw new BadCredentialsException("Invalid credentials");
}

Prevention

When it happens

Trigger: User submits a credential whose transformed password, encoded via the configured PasswordEncoder (e.g. Md5PasswordEncoder with saltFieldName), does not equal the value in the column named by properties.getPasswordFieldName() of the single row returned by the SQL query.

Common situations: Wrong encoder/digest algorithm configured for the schema (SHA vs MD5 vs bcrypt), salt column missing or misnamed, password stored uppercase/lowercase vs digest casing, charset/encoding mismatch, user typo.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-jdbc-authentication/src/main/java/org/apereo/cas/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:58

    public QueryAndEncodeDatabaseAuthenticationHandler(final QueryEncodeJdbcAuthenticationProperties properties,

                                                       final PrincipalFactory principalFactory,
                                                       final DataSource dataSource,
                                                       final DatabasePasswordEncoder databasePasswordEncoder) {
        super(properties, principalFactory, dataSource);
        this.databasePasswordEncoder = databasePasswordEncoder;
    }

    @Override
    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(
        final UsernamePasswordCredential transformedCredential, final String originalPassword) throws Throwable {
        val username = transformedCredential.getUsername();
        try {
            val sqlQueryResults = performSqlQuery(username);
            val digestedPassword = databasePasswordEncoder.encode(transformedCredential.toPassword(), sqlQueryResults);

            if (!sqlQueryResults.get(properties.getPasswordFieldName()).equals(digestedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
            if (StringUtils.isNotBlank(properties.getExpiredFieldName()) && sqlQueryResults.containsKey(properties.getExpiredFieldName())) {
                val dbExpired = sqlQueryResults.get(properties.getExpiredFieldName()).toString();
                if (BooleanUtils.toBoolean(dbExpired) || "1".equals(dbExpired)) {
                    throw new AccountPasswordMustChangeException("Password has expired");
                }
            }
            if (StringUtils.isNotBlank(properties.getDisabledFieldName()) && sqlQueryResults.containsKey(properties.getDisabledFieldName())) {
                val dbDisabled = sqlQueryResults.get(properties.getDisabledFieldName()).toString();
                if (BooleanUtils.toBoolean(dbDisabled) || "1".equals(dbDisabled)) {
                    throw new AccountDisabledException("Account has been disabled");
                }
            }
            val attributes = collectPrincipalAttributes(sqlQueryResults);
            val principal = principalFactory.createPrincipal(username, attributes);
            return createHandlerResult(transformedCredential, principal, new ArrayList<>());
        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {

View on GitHub (pinned to e7288fc434)