{"record":{"id":"1b21c5fbf5eb01d1","repo":"apereo/cas","slug":"unable-to-authorize-surrogate-authentication-reque-1b21c5","errorCode":null,"errorMessage":"Unable to authorize surrogate authentication request for ","messagePattern":"Unable to authorize surrogate authentication request for ","errorType":"exception","errorClass":"SurrogateAuthenticationException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/authentication/rest/SurrogateAuthenticationRestHttpRequestCredentialFactory.java","lineNumber":62,"sourceCode":"    }\n\n    @Override\n    public List<Credential> fromRequest(final HttpServletRequest request, final MultiValueMap<String, String> requestBody) throws Throwable {\n        val credentials = super.fromRequest(request, requestBody);\n        if (credentials.isEmpty()) {\n            return credentials;\n        }\n        val credential = FunctionUtils.doUnchecked(() -> extractCredential(request, credentials));\n        if (credential == null) {\n            LOGGER.trace(\"Not a surrogate authentication attempt, returning parent class credentials\");\n            return credentials;\n        }\n        val surrogateAccounts = surrogateAuthenticationService.getImpersonationAccounts(credential.getId(), Optional.empty());\n        val surrogateUsername = credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class)\n            .map(SurrogateCredentialTrait::getSurrogateUsername)\n            .orElseThrow();\n        if (!surrogateAccounts.contains(surrogateUsername)) {\n            throw new SurrogateAuthenticationException(\n                \"Unable to authorize surrogate authentication request for \" + surrogateUsername);\n        }\n        return CollectionUtils.wrapList(prepareCredential(request, credential));\n    }\n\n    protected @Nullable MutableCredential extractCredential(final HttpServletRequest request,\n                                                            final List<Credential> credentials) {\n        val credential = (MutableCredential) credentials.getFirst();\n        if (credential != null) {\n            var surrogateUsername = request.getHeader(REQUEST_HEADER_SURROGATE_PRINCIPAL);\n            if (StringUtils.isNotBlank(surrogateUsername)) {\n                LOGGER.debug(\"Request surrogate principal [{}]\", surrogateUsername);\n                credential.getCredentialMetadata().addTrait(new SurrogateCredentialTrait(surrogateUsername));\n                return credential;\n            }\n\n            val username = credential.getId();\n            val separator = properties.getCore().getSeparator();","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/authentication/rest/SurrogateAuthenticationRestHttpRequestCredentialFactory.java#L44-L80","documentation":"CAS throws SurrogateAuthenticationException in SurrogateAuthenticationRestHttpRequestCredentialFactory.fromRequest when a REST credential carries a SurrogateCredentialTrait whose surrogateUsername is not among the impersonation accounts the SurrogateAuthenticationService returns for the authenticated principal. CAS only allows a principal to impersonate accounts explicitly authorized (e.g. via surrogate eligible groups/attributes), so an unlisted target username is rejected before any surrogate credential is prepared.","triggerScenarios":"A REST client submits credentials with a surrogate trait (principal + surrogate username) whose surrogateUsername is not in surrogateAuthenticationService.getImpersonationAccounts(principalId, empty).","commonSituations":"Typo in the surrogate username; user not member of the configured surrogate-eligible attribute/group (cas.authn.surrogate.simple.surrogateGroups / surrogateAttributes); using wildcard like '*' when separation pattern is not configured; expecting admin-right impersonation without configuring an eligible surrogate search.","solutions":["Verify the surrogate username exists and is spelled correctly; it must be an account the principal is allowed to impersonate","Add the principal to the surrogate eligibility source (surrogateGroups/surrogateAttributes for the simple service, or the LDAP/custom SurrogateAuthenticationService repository)","If all members of a group may impersonate anyone, use the wildcard separator (e.g. 'user~*') so getImpersonationAccounts returns '*'","Debug surrogateAuthenticationService.getImpersonationAccounts(principal) to confirm the expected accounts are returned"],"exampleFix":"// before\ncas.authn.surrogate.simple.surrogateGroups=\n// after\ncas.authn.surrogate.simple.surrogateGroups=impersonators\ncas.authn.surrogate.simple.surrogateAttributes=empId","handlingStrategy":"validation","validationCode":"var accounts = surrogateAuthenticationService.getImpersonationAccounts(credential.getId(), Optional.empty());\nif (trait.map(SurrogateCredentialTrait::getSurrogateUsername).map(u -> !accounts.contains(u)).orElse(true)) {\n    throw new IllegalArgumentException(\"surrogate username not authorized for \" + credential.getId());\n}","typeGuard":null,"tryCatchPattern":"try {\n    return factory.fromRequest(request, credential);\n} catch (SurrogateAuthenticationException e) {\n    LOGGER.warn(\"Surrogate not authorized: {}\", e.getMessage());\n    return CollectionUtils.wrapList(credential); // fall back to principal-only authn\n}","preventionTips":["Always call getImpersonationAccounts() for the principal before constructing the surrogate credential","Use CAS's own surrogate eligibility REST/API to preview allowed impersonations","Keep group/attribute eligibility config in sync with the IdP data","Log and alert on repeated surrogate authorization failures (possible probing)"],"tags":["surrogate-authentication","impersonation-unauthorized","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"}