{"record":{"id":"de8bb8ebb7f9ea7f","repo":"apereo/cas","slug":"account-password-must-change-for","errorCode":null,"errorMessage":"Account password must change for ","messagePattern":"Account password must change for ","errorType":"exception","errorClass":"AccountPasswordMustChangeException","httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-syncope-authentication/src/main/java/org/apereo/cas/syncope/SyncopeAuthenticationHandler.java","lineNumber":66,"sourceCode":"                                        final String syncopeDomain) {\n        super(properties.getName(), principalFactory, properties.getOrder());\n        this.properties = properties;\n        this.syncopeDomain = syncopeDomain;\n    }\n\n    @Override\n    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(\n        final UsernamePasswordCredential credential, @Nullable final String originalPassword) throws Throwable {\n        val result = authenticateSyncopeUser(credential);\n        if (result.isPresent()) {\n            val user = result.get();\n            LOGGER.debug(\"Received Syncope user object as [{}]\", user);\n            if (user.has(\"suspended\") && user.get(\"suspended\").asBoolean()) {\n                throw new AccountDisabledException(\n                    \"Could not authenticate forbidden account for \" + credential.getUsername());\n            }\n            if (user.has(\"mustChangePassword\") && user.get(\"mustChangePassword\").asBoolean()) {\n                throw new AccountPasswordMustChangeException(\n                    \"Account password must change for \" + credential.getUsername());\n            }\n            val principalAttributes = SyncopeUtils.convertFromUserEntity(user, properties.getAttributeMappings());\n            val name = properties.getAttributeMappings().getOrDefault(\"domain\", \"syncopeDomain\");\n            principalAttributes.put(name, CollectionUtils.wrapList(syncopeDomain));\n            val principal = principalFactory.createPrincipal(user.get(\"username\").asString(), principalAttributes);\n            return createHandlerResult(credential, principal, new ArrayList<>());\n        }\n        throw new FailedLoginException(\"Could not authenticate account for \" + credential.getUsername());\n    }\n\n    protected Optional<JsonNode> authenticateSyncopeUser(final UsernamePasswordCredential credential) {\n        HttpResponse response = null;\n        try {\n            val syncopeRestUrl = Strings.CI.appendIfMissing(\n                SpringExpressionLanguageValueResolver.getInstance().resolve(properties.getUrl()),\n                \"/rest/users/self\");\n            val exec = HttpExecutionRequest.builder()","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-syncope-authentication/src/main/java/org/apereo/cas/syncope/SyncopeAuthenticationHandler.java#L48-L84","documentation":"SyncopeAuthenticationHandler.authenticateUsernamePasswordInternal throws AccountPasswordMustChangeException when Syncope's user object has mustChangePassword == true. Syncope enforces a password-change requirement (expired or admin-forced reset), and CAS surfaces it as a special AuthenticationException so the webflow can route the user to a change-password flow instead of logging them in.","triggerScenarios":"Successful credential lookup where the returned Syncope user JSON contains \"mustChangePassword\": true; the user then must complete the password-change action before CAS will issue a handler result.","commonSituations":"Admin set forceChangePassword / pwdPolicy expiring password; password aged out per Syncope password policy; freshly provisioned user with a temporary password; missing CAS password-management (pwdmgmt) configuration so users can never complete the change and stay stuck.","solutions":["Configure CAS password management for Syncope (cas.authn.pwdmgmt.syncope.*) so the mustChangePassword condition routes to the change-password flow","Have the user change their password in Syncope (self-service console) or admin clears the mustChangePassword flag","Adjust Syncope password policy/reset token expiration if it expires too aggressively","Verify CAS version supports the Syncope password management module and it's on the classpath"],"exampleFix":"// before\ncas.authn.syncope.url=https://syncope.example.org/syncope\n// after\ncas.authn.syncope.url=https://syncope.example.org/syncope\ncas.authn.pwdmgmt.syncope.url=https://syncope.example.org/syncope\ncas.authn.pwdmgmt.syncope.domain=Two","handlingStrategy":"try-catch","validationCode":"JsonNode user = fetchSyncopeUser(u, p);\nif (user != null && user.path(\"mustChangePassword\").asBoolean(false)) {\n    redirect(\"/cas/login?changePassword=true\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    return authenticationManager.authenticate(transaction);\n} catch (AccountPasswordMustChangeException e) {\n    return \"casMustChangePasswordView\"; // route to pwdmgmt flow\n}","preventionTips":["Configure cas.authn.pwdmgmt.* for Syncope so users get a change-password flow","Set reasonable password policy / expiry in Syncope","Provision users with non-expiring initial passwords plus forced-change flag and a working flow","Test the full change-password round-trip before rollout"],"tags":["syncope","password-expired","authentication","cas"],"backgroundTag":"authentication-required","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"}