{"record":{"id":"30d1d9ea2e826f8b","repo":"apereo/cas","slug":"password-has-expired","errorCode":null,"errorMessage":"Password has expired","messagePattern":"Password has expired","errorType":"exception","errorClass":"AccountPasswordMustChangeException","httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-jdbc-authentication/src/main/java/org/apereo/cas/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java","lineNumber":63,"sourceCode":"        super(properties, principalFactory, dataSource);\n        this.databasePasswordEncoder = databasePasswordEncoder;\n    }\n\n    @Override\n    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(\n        final UsernamePasswordCredential transformedCredential, final String originalPassword) throws Throwable {\n        val username = transformedCredential.getUsername();\n        try {\n            val sqlQueryResults = performSqlQuery(username);\n            val digestedPassword = databasePasswordEncoder.encode(transformedCredential.toPassword(), sqlQueryResults);\n\n            if (!sqlQueryResults.get(properties.getPasswordFieldName()).equals(digestedPassword)) {\n                throw new FailedLoginException(\"Password does not match value on record.\");\n            }\n            if (StringUtils.isNotBlank(properties.getExpiredFieldName()) && sqlQueryResults.containsKey(properties.getExpiredFieldName())) {\n                val dbExpired = sqlQueryResults.get(properties.getExpiredFieldName()).toString();\n                if (BooleanUtils.toBoolean(dbExpired) || \"1\".equals(dbExpired)) {\n                    throw new AccountPasswordMustChangeException(\"Password has expired\");\n                }\n            }\n            if (StringUtils.isNotBlank(properties.getDisabledFieldName()) && sqlQueryResults.containsKey(properties.getDisabledFieldName())) {\n                val dbDisabled = sqlQueryResults.get(properties.getDisabledFieldName()).toString();\n                if (BooleanUtils.toBoolean(dbDisabled) || \"1\".equals(dbDisabled)) {\n                    throw new AccountDisabledException(\"Account has been disabled\");\n                }\n            }\n            val attributes = collectPrincipalAttributes(sqlQueryResults);\n            val principal = principalFactory.createPrincipal(username, attributes);\n            return createHandlerResult(transformedCredential, principal, new ArrayList<>());\n        } catch (final IncorrectResultSizeDataAccessException e) {\n            if (e.getActualSize() == 0) {\n                throw new AccountNotFoundException(username + \" not found with SQL query\");\n            }\n            throw new FailedLoginException(\"Multiple records found for \" + username);\n        } catch (final DataAccessException e) {\n            throw new PreventedException(e);","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-jdbc-authentication/src/main/java/org/apereo/cas/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java#L45-L81","documentation":"The handler throws AccountPasswordMustChangeException('Password has expired') when the boolean/flag column named by properties.getExpiredFieldName() in the query result row is truthy ('true', 'yes', '1', etc.). The credential itself was correct; the account's password is flagged expired and the user must change it.","triggerScenarios":"SQL query returns a row whose expired-flag column (configurable expiredFieldName) parses as BooleanUtils.toBoolean(dbExpired) or equals \"1\".","commonSituations":"Database password-aging columns (e.g. pwd_changed or expired) set by an admin or policy job, schema uses 1/0 tinyint flags, misconfigured column accidentally points at a flag that is always 1.","solutions":["Have the user change their password so the expired flag clears","Confirm expiredFieldName matches the intended schema column; if the column is always set, fix the mapping","If expiry is unexpected, update the flag to false/0 in the database","Wire an account-status handling flow (e.g. password management) instead of plain rejection"],"exampleFix":"// before\n// cas.authn.jdbc.encode[0].fieldExpired=expired\n// after (column actually stores last-change date, not a flag)\n// cas.authn.jdbc.encode[0].fieldExpired=password_expired","handlingStrategy":"try-catch","validationCode":"// Pre-check expiry outside CAS\nboolean expired = jdbc.queryForObject(\"SELECT password_expired FROM users WHERE username=?\", Boolean.class, user);\nif (expired) forcePasswordChangeFlow(user);","typeGuard":null,"tryCatchPattern":"try {\n    authResult = handler.authenticate(credential);\n} catch (AccountPasswordMustChangeException e) {\n    // credential was valid; route to password-change flow\n    return redirectToPasswordChange(user);\n}","preventionTips":["Map fieldExpired/fieldExpired to the correct expiry-flag column","Automate password-rotation so flags clear before expiry","Distinguish AccountPasswordMustChangeException from FailedLoginException in the UI flow","Audit who/what sets the expired flag"],"tags":["jdbc","account-status","password-expiry"],"backgroundTag":"password-expired","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}