{"record":{"id":"c992153e4b7f8e56","repo":"apereo/cas","slug":"could-not-authenticate-locked-account-for-userna","errorCode":null,"errorMessage":"Could not authenticate locked account for ${username}","messagePattern":"Could not authenticate locked account for (.+?)","errorType":"exception","errorClass":"AccountLockedException","httpStatus":423,"severity":"error","filePath":"support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java","lineNumber":96,"sourceCode":"        var response = (HttpResponse) null;\n        try {\n            val exec = HttpExecutionRequest\n                .builder()\n                .basicAuthUsername(credential.getUsername())\n                .basicAuthPassword(credential.toPassword())\n                .method(HttpMethod.valueOf(properties.getMethod().toUpperCase(Locale.ENGLISH)))\n                .url(SpringExpressionLanguageValueResolver.getInstance().resolve(properties.getUri()))\n                .httpClient(httpClient)\n                .build()\n                .withoutRetry();\n            response = HttpUtils.execute(exec);\n            val status = HttpStatus.resolve(Objects.requireNonNull(response).getCode());\n            return switch (Objects.requireNonNull(status)) {\n                case OK -> buildPrincipalFromResponse(credential, response);\n                case FORBIDDEN -> throw new AccountDisabledException(\"Could not authenticate forbidden account for \" + credential.getUsername());\n                case UNAUTHORIZED -> throw new FailedLoginException(\"Could not authenticate account for \" + credential.getUsername());\n                case NOT_FOUND -> throw new AccountNotFoundException(\"Could not locate account for \" + credential.getUsername());\n                case LOCKED -> throw new AccountLockedException(\"Could not authenticate locked account for \" + credential.getUsername());\n                case PRECONDITION_FAILED -> throw new AccountExpiredException(\"Could not authenticate expired account for \" + credential.getUsername());\n                case PRECONDITION_REQUIRED -> throw new AccountPasswordMustChangeException(\"Account password must change for \" + credential.getUsername());\n                default -> throw new FailedLoginException(\"Rest endpoint returned an unknown status code \" + status + \" for \" + credential.getUsername());\n            };\n        } finally {\n            HttpUtils.close(response);\n        }\n    }\n\n    protected AuthenticationHandlerExecutionResult buildPrincipalFromResponse(\n        final UsernamePasswordCredential credential,\n        final HttpResponse response) throws Throwable {\n        try {\n            try (val content = ((HttpEntityContainer) response).getEntity().getContent()) {\n                val result = IOUtils.toString(content, StandardCharsets.UTF_8);\n                LOGGER.debug(\"REST authentication response received: [{}]\", result);\n                val principalFromRest = MAPPER.readValue(result, Principal.class);\n                val principal = principalFactory.createPrincipal(principalFromRest.getId(), principalFromRest.getAttributes());","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java#L78-L114","documentation":"RestAuthenticationHandler throws AccountLockedException when the remote REST authentication endpoint replies HTTP 423 LOCKED. CAS interprets this as the account being administratively locked on the remote side, rejecting authentication even with valid credentials.","triggerScenarios":"authenticateUsernamePasswordInternal posts credentials; the endpoint responds 423 LOCKED and the switch maps LOCKED -> AccountLockedException.","commonSituations":"Remote system locked the account after repeated failed attempts; admin manually locked the user; a middlebox returns 423 for unrelated reasons and CAS misreads it as account lockout.","solutions":["Unlock the account in the remote system and retry","Review the remote system's lockout policy/thresholds","Verify no proxy/CDN is generating 423 responses unrelated to account state","Coordinate CAS throttling settings with the remote lockout so both systems agree"],"exampleFix":"// before: remote store\n{\"user\":\"jdoe\",\"locked\":true}\n// after unlock\n{\"user\":\"jdoe\",\"locked\":false}","handlingStrategy":"try-catch","validationCode":"val acct = remoteUserStore.lookup(username);\nif (acct != null && acct.isLocked()) {\n    throw new AccountLockedException(\"Account locked upstream\");\n}","typeGuard":"boolean isLocked(HttpResponse r) { return r != null && r.getCode() == 423; }","tryCatchPattern":"try {\n    return restHandler.authenticate(credential);\n} catch (AccountLockedException e) {\n    LOGGER.warn(\"Locked account: {}\", e.getMessage());\n    throw e; // route to unlock workflow\n}","preventionTips":["Align lockout thresholds between CAS and the remote system","Provide a self-service or admin unlock path","Monitor 423 rates to detect brute-force or policy mismatch","Verify no middleware fabricates 423 responses"],"tags":["authentication","rest","http-423","account-locked"],"backgroundTag":"http-error-response","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"}