{"record":{"id":"a9725fd5b008b588","repo":"apereo/cas","slug":"could-not-authenticate-forbidden-account-for-use","errorCode":null,"errorMessage":"Could not authenticate forbidden account for ${username}","messagePattern":"Could not authenticate forbidden account for (.+?)","errorType":"exception","errorClass":"AccountDisabledException","httpStatus":403,"severity":"error","filePath":"support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java","lineNumber":93,"sourceCode":"        final UsernamePasswordCredential credential,\n        final String originalPassword) throws Throwable {\n\n        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);","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java#L75-L111","documentation":"RestAuthenticationHandler throws AccountDisabledException when the remote REST authentication endpoint replies HTTP 403 FORBIDDEN. CAS interprets 403 as a disabled account rather than a bad password, and the username is embedded in the message for troubleshooting.","triggerScenarios":"authenticateUsernamePasswordInternal posts credentials to the configured REST endpoint via HttpUtils; the endpoint responds with status 403 and the switch maps FORBIDDEN -> AccountDisabledException.","commonSituations":"The endpoint's user account was deactivated; a firewall/WAF or proxy returns 403 for blocked requests, misread as a disabled account; the remote service disabled the user after policy violations.","solutions":["Check the remote system and re-enable the user account","Verify what actually returns 403: hit the endpoint manually with the same payload to rule out proxies/WAFs","Confirm the REST endpoint URL and auth configuration in cas.authn.rest point at the intended service","If your endpoint uses 403 for generic denial, adjust the endpoint or a custom handler mapping"],"exampleFix":"// before: endpoint returns 403 for deactivated user\nHTTP/1.1 403 Forbidden\n// after re-enabling the account\nHTTP/1.1 200 OK","handlingStrategy":"try-catch","validationCode":"// Pre-check the endpoint before user auth\ncurl -s -o /dev/null -w \"%{http_code}\" $CAS_REST_AUTH_URL  # expect non-403 for a healthy route","typeGuard":"boolean isForbidden(HttpResponse r) { return r != null && r.getCode() == 403; }","tryCatchPattern":"try {\n    return restHandler.authenticate(credential);\n} catch (AccountDisabledException e) {\n    LOGGER.warn(\"Disabled account or blocked request: {}\", e.getMessage());\n    throw e;\n}","preventionTips":["Keep remote accounts' enabled/disabled state in sync","Test the endpoint manually with curl before blaming the user","Ensure no WAF/proxy returns 403 for reasons unrelated to account state","Document the 403 semantics contract with the endpoint team"],"tags":["authentication","rest","http-403","account-disabled"],"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"}