{"record":{"id":"3a22e957861f94e6","repo":"apereo/cas","slug":"unable-to-detect-the-authentication-principal-for","errorCode":null,"errorMessage":"Unable to detect the authentication principal for ${username}","messagePattern":"Unable to detect the authentication principal for (.+?)","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java","lineNumber":119,"sourceCode":"        } 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());\n                return createHandlerResult(credential, principal, getWarnings(response));\n            }\n        } catch (final Throwable e) {\n            LoggingUtils.error(LOGGER, e);\n            throw new FailedLoginException(\"Unable to detect the authentication principal for \" + credential.getUsername());\n        }\n    }\n\n    /**\n     * Resolve {@link MessageDescriptor warnings} from the response.\n     *\n     * @param authenticationResponse The response sent by the REST authentication endpoint\n     * @return The warnings for the created {@link AuthenticationHandlerExecutionResult}\n     */\n    protected List<MessageDescriptor> getWarnings(final HttpResponse authenticationResponse) {\n        val messageDescriptors = new ArrayList<MessageDescriptor>(2);\n\n        val passwordExpirationDate = authenticationResponse.getFirstHeader(HEADER_NAME_CAS_PASSWORD_EXPIRATION_DATE);\n        if (passwordExpirationDate != null) {\n            val days = Duration.between(Instant.now(Clock.systemUTC()), DateTimeUtils.convertToZonedDateTime(passwordExpirationDate.getValue())).toDays();\n            messageDescriptors.add(new PasswordExpiringWarningMessageDescriptor(null, days));\n        }\n","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-rest-authentication/src/main/java/org/apereo/cas/adaptors/rest/RestAuthenticationHandler.java#L101-L137","documentation":"RestAuthenticationHandler throws this FailedLoginException from buildPrincipalFromResponse when the successful (HTTP 200) REST response body cannot be turned into a CAS Principal — typically because the JSON does not deserialize into a Principal (missing/invalid id field) or parsing throws. Any Throwable in the parsing path is logged via LoggingUtils and converted into this failed login.","triggerScenarios":"authenticateUsernamePasswordInternal gets status OK, calls buildPrincipalFromResponse, and MAPPER.readValue(result, Principal.class) or principalFactory.createPrincipal throws — e.g. empty body, non-JSON body, JSON lacking '@class'/id, or unexpected attribute types.","commonSituations":"Endpoint returns an empty or HTML error body with status 200; response JSON does not match the expected Principal structure (missing id, Jackson needs @class typing info); endpoint API changed its response shape after an upgrade; attribute values are of types Jackson cannot map.","solutions":["Inspect the actual response body (curl the endpoint) and confirm it is valid JSON with an id and attributes matching CAS Principal format","Add the Jackson @class type info or align the endpoint payload with the expected Principal shape","Check CAS logs above this message for the underlying deserialization exception to pinpoint the field","If the endpoint returns 200 with an empty body for unknown users, fix the endpoint to return 404 instead"],"exampleFix":"// before (endpoint response)\n{\"name\":\"jdoe\"}\n// after (valid CAS Principal payload)\n{\"@class\":\"org.apereo.cas.authentication.principal.SimplePrincipal\",\"id\":\"jdoe\",\"attributes\":{\"email\":[\"jdoe@example.org\"]}}","handlingStrategy":"try-catch","validationCode":"// Verify the endpoint's success payload parses as a CAS Principal before wiring it in\nString body = httpClient.get(restAuthUrl);\nJsonNode node = MAPPER.readTree(body);\nif (node == null || !node.hasNonNull(\"id\")) {\n    throw new IllegalStateException(\"Response missing principal id\");\n}","typeGuard":"boolean isValidPrincipalPayload(String body) {\n    try {\n        JsonNode n = MAPPER.readTree(body);\n        return n != null && n.hasNonNull(\"id\") && n.get(\"id\").isTextual();\n    } catch (Exception e) { return false; }\n}","tryCatchPattern":"try {\n    return restHandler.authenticate(credential);\n} catch (FailedLoginException e) {\n    if (e.getMessage().startsWith(\"Unable to detect the authentication principal\")) {\n        LOGGER.error(\"Principal parse failure — check response shape: {}\", e.getMessage());\n    }\n    throw e;\n}","preventionTips":["Contract-test the endpoint's 200 payload against CAS Principal deserialization","Include Jackson @class typing info or match the expected Principal JSON shape","Log the raw response body (safely) when deserialization fails","Pin endpoint API versions so response shapes do not shift unexpectedly"],"tags":["authentication","rest","json-deserialization","principal-resolution"],"backgroundTag":"unexpected-response-shape","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"}