{"record":{"id":"d241e11c646fa891","repo":"apereo/cas","slug":"password-has-expired-d241e1","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/QueryDatabaseAuthenticationHandler.java","lineNumber":92,"sourceCode":"                        + username + \" or value not parseable as a number\");\n                }\n\n                val number = NumberUtils.createNumber(count.toString());\n                if (number.longValue() != 1) {\n                    throw new FailedLoginException(\"No records found for user \" + username);\n                }\n            }\n\n            if (StringUtils.isNotBlank(properties.getFieldDisabled()) && dbFields.containsKey(properties.getFieldDisabled())) {\n                val dbDisabled = dbFields.get(properties.getFieldDisabled()).toString();\n                if (BooleanUtils.toBoolean(dbDisabled) || \"1\".equals(dbDisabled)) {\n                    throw new AccountDisabledException(\"Account has been disabled\");\n                }\n            }\n            if (StringUtils.isNotBlank(properties.getFieldExpired()) && dbFields.containsKey(properties.getFieldExpired())) {\n                val dbExpired = dbFields.get(properties.getFieldExpired()).toString();\n                if (BooleanUtils.toBoolean(dbExpired) || \"1\".equals(dbExpired)) {\n                    throw new AccountPasswordMustChangeException(\"Password has expired\");\n                }\n            }\n\n            val attributes = collectPrincipalAttributes(dbFields);\n            val principal = this.principalFactory.createPrincipal(username, attributes);\n            return createHandlerResult(credential, principal, new ArrayList<>());\n\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);\n        }\n    }\n\n    protected Map<String, Object> query(final UsernamePasswordCredential credential) {","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-jdbc-authentication/src/main/java/org/apereo/cas/jdbc/QueryDatabaseAuthenticationHandler.java#L74-L110","documentation":"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.","triggerScenarios":"cas.authn.jdbc.query[].fieldExpired is configured and the row returned by sqlFindByUsername contains that column with value 'true'/'1'/'yes' or true.","commonSituations":"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.","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."],"exampleFix":"// before: fieldExpired=account_status (holds 1 for many users)\n// after: point at the dedicated expiry column and clear stale flags\ncas.authn.jdbc.query[0].fieldExpired=password_expired\nUPDATE users SET password_expired=0 WHERE password_expired=1 AND password_last_changed < NOW() - INTERVAL '90 days';","handlingStrategy":"validation","validationCode":"// Check the expiry flag for the user before calling authenticate\nboolean expired = jdbcTemplate.queryForObject(\n    \"SELECT COALESCE(password_expired,0) FROM users WHERE username=?\", Integer.class, username) == 1;\nif (expired) { forcePasswordChangeFlow(); }","typeGuard":null,"tryCatchPattern":"// catch the specific CAS exception\ntry { handlerResult = authHandler.authenticate(credential); }\ncatch (AccountPasswordMustChangeException e) { redirectToPasswordChange(); }","preventionTips":["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."],"tags":["jdbc","authentication","password-expired"],"backgroundTag":"account-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"}