{"record":{"id":"6d6043dcfe7769d1","repo":"apereo/cas","slug":"invalid-response-format-received-from-duo","errorCode":null,"errorMessage":"Invalid response format received from Duo","messagePattern":"Invalid response format received from Duo","errorType":"exception","errorClass":"DuoSecurityException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-duo-core/src/main/java/org/apereo/cas/adaptors/duo/authn/BaseDuoSecurityAuthenticationService.java","lineNumber":95,"sourceCode":"            LOGGER.debug(\"Found cached duo user account [{}]\", account);\n            return account;\n        }\n\n        val account = new DuoSecurityUserAccount(username);\n        account.setStatus(DuoSecurityUserAccountStatus.AUTH);\n\n        try {\n            val userRequest = buildHttpPostUserPreAuthRequest(username);\n            signHttpUserPreAuthRequest(userRequest);\n            LOGGER.debug(\"Contacting Duo Security to inquire about username [{}]\", username);\n            val userResponse = getHttpResponse(userRequest);\n            val jsonResponse = URLDecoder.decode(userResponse, StandardCharsets.UTF_8);\n            LOGGER.debug(\"Received Duo response [{}]\", jsonResponse);\n\n            val result = MAPPER.readTree(jsonResponse);\n            if (!result.has(RESULT_KEY_STAT)) {\n                LOGGER.warn(\"Duo response was received in unknown format: [{}]\", jsonResponse);\n                throw new DuoSecurityException(\"Invalid response format received from Duo\");\n            }\n\n            if (\"OK\".equalsIgnoreCase(result.get(RESULT_KEY_STAT).asString())) {\n                val response = result.get(RESULT_KEY_RESPONSE);\n                val authResult = response.get(RESULT_KEY_RESULT).asString().toUpperCase(Locale.ENGLISH);\n\n                val status = DuoSecurityUserAccountStatus.valueOf(authResult);\n                account.setProviderId(properties.getId());\n                account.setStatus(status);\n                account.setMessage(response.get(RESULT_KEY_STATUS_MESSAGE).asString());\n                if (status == DuoSecurityUserAccountStatus.ENROLL) {\n                    val enrollUrl = response.get(RESULT_KEY_ENROLL_PORTAL_URL).asString();\n                    account.setEnrollPortalUrl(enrollUrl);\n                }\n            } else {\n                val code = result.get(RESULT_KEY_CODE).asInt();\n                if (code > RESULT_CODE_ERROR_THRESHOLD) {\n                    LOGGER.warn(\"Duo returned a failure response with code: [{}]. Duo will be considered unavailable\",","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-duo-core/src/main/java/org/apereo/cas/adaptors/duo/authn/BaseDuoSecurityAuthenticationService.java#L77-L113","documentation":"DuoSecurityException thrown by BaseDuoSecurityAuthenticationService.getUserAccount() when the JSON returned by the Duo Admin/API endpoint has no \"stat\" field at all. The service expects every Duo API response to carry \"stat\" (\"OK\" or \"FAIL\"); its absence means the payload is not a recognizable Duo API response. The service cannot tell success from failure, so it treats Duo as unusable and aborts.","triggerScenarios":"Calling getUserAccount (via DuoSecurityAuthenticationService) when the decoded Duo HTTP response body parses as JSON but lacks the \"stat\" key — e.g. the response is a JSON error object from a proxy/gateway, an HTML error page that happens to parse, or a Duo response from an incompatible API version.","commonSituations":"Wrong integration key/secret pointing at a non-Duo endpoint; a reverse proxy or SSO gateway intercepting the request and returning its own JSON (e.g. {\"error\":\"unauthorized\"}); Duo API hostname misconfigured (typo in api-xxxxxxxx.duosecurity.com); network appliance returning canned JSON error bodies.","solutions":["Verify the Duo API hostname, integration key (ikey) and secret key (skey) in cas.authn.mfa.duo[0].* configuration — a wrong hostname often yields a foreign JSON body without \"stat\".","Enable debug logging (org.apereo.cas.adaptors.duograde to DEBUG) and inspect the logged \"Duo response was received in unknown format\" payload to identify who actually produced the JSON.","Check for intermediaries (corporate proxies, API gateways, WAFs) between CAS and Duo that may replace the response body; bypass or allowlist the Duo endpoint.","Confirm the Duo integration type is supported and the account/API version still returns the classic {stat:...} envelope."],"exampleFix":"// before: response body from wrong endpoint\n{\"errors\":[{\"code\":\"invalid_request\"}]}\n\n// after: correct Duo endpoint configured\ncas.authn.mfa.duo[0].duo-api-host=api-xxxxxxxx.duosecurity.com\ncas.authn.mfa.duo[0].integration-key=DI...\ncas.authn.mfa.duo[0].secret-key=...","handlingStrategy":"validation","validationCode":"// Java: validate Duo response shape before relying on it\nJsonNode result = MAPPER.readTree(jsonResponse);\nif (result == null || !result.hasNonNull(\"stat\")) {\n    throw new IllegalArgumentException(\"Duo response missing 'stat' field: \" + jsonResponse);\n}","typeGuard":"boolean isDuoApiResponse(JsonNode n) {\n    return n != null && n.isObject() && n.hasNonNull(\"stat\") && n.get(\"stat\").isTextual();\n}","tryCatchPattern":"try {\n    account = duoService.getUserAccount(username);\n} catch (DuoSecurityException e) {\n    // mark Duo unavailable, fall back / surface config error\n    LOGGER.error(\"Duo unusable: {}\", e.getMessage());\n}","preventionTips":["Verify Duo api host, ikey, and skey at deploy time with a /check ping call.","Inspect raw Duo response bodies in debug logs during initial setup.","Bypass/allowlist corporate proxies for *.duosecurity.com endpoints."],"tags":["duo","mfa","api-response","http-client"],"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"}