apereo/cas · warning · AccountPasswordMustChangeException
AccountPasswordMustChangeException
Error message
AccountPasswordMustChangeException
What it means
RedisAuthenticationHandler throws AccountPasswordMustChangeException when the Redis account record has status MUST_CHANGE_PASSWORD. CAS signals that authentication may proceed administratively but the user must change their password before being fully admitted, typically routing the flow to a password-change screen.
Solutions
- Have the user complete the CAS password-change flow to clear the must-change flag
- Reset/clear the MUST_CHANGE_PASSWORD status in the Redis account record once the password is updated
- Check the provisioning source that sets this status if it was set unintentionally
Example fix
// before (stored in Redis)
{"username":"jdoe","status":"MUST_CHANGE_PASSWORD"}
// after password change
{"username":"jdoe","status":"OK"} Defensive patterns
Strategy: try-catch
Validate before calling
val acct = redisAccountService.findAccount(username);
if (acct != null && acct.getStatus() == AccountStatus.MUST_CHANGE_PASSWORD) {
// redirect to password change flow instead of authenticating
} Type guard
boolean needsPasswordChange(AccountState s) {
return s != null && s.getStatus() == AccountStatus.MUST_CHANGE_PASSWORD;
} Try / catch
try {
return authenticationHandler.authenticate(credential);
} catch (AccountPasswordMustChangeException e) {
// send user to the password-change webflow
throw e;
} Prevention
- Ensure CAS password management is configured so users can clear the flag
- Provision users with OK status unless a reset is truly required
- Sync must-change flags from your IAM system deliberately
- Test the full password-change round trip in staging
When it happens
Trigger: authenticateUsernamePasswordInternal loads the account from Redis, the password matches, but account.getStatus() is MUST_CHANGE_PASSWORD in the status switch.
Common situations: A password expired or an admin forced a reset flag in the Redis user store; an external provisioning system marked the account must-change; user was migrated with a forced-reset flag set.
Related errors
- Password has expired
- AccountNotFoundException
- FailedLoginException
- AccountDisabledException
- AccountExpiredException
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/9b20ebd2c6a5b0bd.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-redis-authentication/src/main/java/org/apereo/cas/redis/RedisAuthenticationHandler.java:47
}
@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)