apereo/cas · warning · AccountPasswordMustChangeException
Password has expired
Error message
Password has expired
What it means
QueryDatabaseAuthenticationHandler checks an 'expired password' column from the SQL result row; if it is truthy (boolean true, '1', etc.), authentication is aborted with AccountPasswordMustChangeException. This signals the credential is correct but the account's password is flagged as expired and must be changed before login.
Solutions
- Have the user change their password so the expired flag clears, then retry.
- Verify the fieldExpired property points at the correct column and that stale rows are updated (UPDATE users SET expired=0).
- If expiry should be handled rather than blocked, adjust cas.authn.jdbc.query[].passwordPolicy settings or clear the column before authentication.
- Check BooleanUtils semantics: values like 'true','on','y','1' all count as expired; normalize the column contents.
Example fix
// before: fieldExpired=account_status (holds 1 for many users) // after: point at the dedicated expiry column and clear stale flags cas.authn.jdbc.query[0].fieldExpired=password_expired UPDATE users SET password_expired=0 WHERE password_expired=1 AND password_last_changed < NOW() - INTERVAL '90 days';
Defensive patterns
Strategy: validation
Validate before calling
// Check the expiry flag for the user before calling authenticate
boolean expired = jdbcTemplate.queryForObject(
"SELECT COALESCE(password_expired,0) FROM users WHERE username=?", Integer.class, username) == 1;
if (expired) { forcePasswordChangeFlow(); } Try / catch
// catch the specific CAS exception
try { handlerResult = authHandler.authenticate(credential); }
catch (AccountPasswordMustChangeException e) { redirectToPasswordChange(); } Prevention
- Keep the expired flag in a dedicated boolean column and clear it after password change.
- Point fieldExpired only at a real boolean/0-1 column.
- Audit legacy rows for stale '1' values after migrations.
When it happens
Trigger: cas.authn.jdbc.query[].fieldExpired is configured and the row returned by sqlFindByUsername contains that column with value 'true'/'1'/'yes' or true.
Common situations: Database password-expiry flag set by DBA policy; misconfigured fieldExpired pointing at an always-true column; schema migration leaving 1 in an expiry column for legacy users.
Related errors
- [e.getMessage()]
- Password does not match value on record.
- Password does not match value on record.
- [username] not found with SQL query.
- Failed to authenticate user
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/d241e11c646fa891.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-jdbc-authentication/src/main/java/org/apereo/cas/jdbc/QueryDatabaseAuthenticationHandler.java:92
+ username + " or value not parseable as a number");
}
val number = NumberUtils.createNumber(count.toString());
if (number.longValue() != 1) {
throw new FailedLoginException("No records found for user " + username);
}
}
if (StringUtils.isNotBlank(properties.getFieldDisabled()) && dbFields.containsKey(properties.getFieldDisabled())) {
val dbDisabled = dbFields.get(properties.getFieldDisabled()).toString();
if (BooleanUtils.toBoolean(dbDisabled) || "1".equals(dbDisabled)) {
throw new AccountDisabledException("Account has been disabled");
}
}
if (StringUtils.isNotBlank(properties.getFieldExpired()) && dbFields.containsKey(properties.getFieldExpired())) {
val dbExpired = dbFields.get(properties.getFieldExpired()).toString();
if (BooleanUtils.toBoolean(dbExpired) || "1".equals(dbExpired)) {
throw new AccountPasswordMustChangeException("Password has expired");
}
}
val attributes = collectPrincipalAttributes(dbFields);
val principal = this.principalFactory.createPrincipal(username, attributes);
return createHandlerResult(credential, principal, new ArrayList<>());
} catch (final IncorrectResultSizeDataAccessException e) {
if (e.getActualSize() == 0) {
throw new AccountNotFoundException(username + " not found with SQL query");
}
throw new FailedLoginException("Multiple records found for " + username);
} catch (final DataAccessException e) {
throw new PreventedException(e);
}
}
protected Map<String, Object> query(final UsernamePasswordCredential credential) {View on GitHub (pinned to e7288fc434)