apereo/cas · error · SurrogateAuthenticationException

Unable to authorize surrogate authentication request for

Error message

Unable to authorize surrogate authentication request for 

What it means

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.

Solutions

  1. Verify the surrogate username exists and is spelled correctly; it must be an account the principal is allowed to impersonate
  2. Add the principal to the surrogate eligibility source (surrogateGroups/surrogateAttributes for the simple service, or the LDAP/custom SurrogateAuthenticationService repository)
  3. If all members of a group may impersonate anyone, use the wildcard separator (e.g. 'user~*') so getImpersonationAccounts returns '*'
  4. Debug surrogateAuthenticationService.getImpersonationAccounts(principal) to confirm the expected accounts are returned

Example fix

// before
cas.authn.surrogate.simple.surrogateGroups=
// after
cas.authn.surrogate.simple.surrogateGroups=impersonators
cas.authn.surrogate.simple.surrogateAttributes=empId
Defensive patterns

Strategy: validation

Validate before calling

var accounts = surrogateAuthenticationService.getImpersonationAccounts(credential.getId(), Optional.empty());
if (trait.map(SurrogateCredentialTrait::getSurrogateUsername).map(u -> !accounts.contains(u)).orElse(true)) {
    throw new IllegalArgumentException("surrogate username not authorized for " + credential.getId());
}

Try / catch

try {
    return factory.fromRequest(request, credential);
} catch (SurrogateAuthenticationException e) {
    LOGGER.warn("Surrogate not authorized: {}", e.getMessage());
    return CollectionUtils.wrapList(credential); // fall back to principal-only authn
}

Prevention

When it happens

Trigger: A REST client submits credentials with a surrogate trait (principal + surrogate username) whose surrogateUsername is not in surrogateAuthenticationService.getImpersonationAccounts(principalId, empty).

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/1b21c5fbf5eb01d1. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/authentication/rest/SurrogateAuthenticationRestHttpRequestCredentialFactory.java:62

    }

    @Override
    public List<Credential> fromRequest(final HttpServletRequest request, final MultiValueMap<String, String> requestBody) throws Throwable {
        val credentials = super.fromRequest(request, requestBody);
        if (credentials.isEmpty()) {
            return credentials;
        }
        val credential = FunctionUtils.doUnchecked(() -> extractCredential(request, credentials));
        if (credential == null) {
            LOGGER.trace("Not a surrogate authentication attempt, returning parent class credentials");
            return credentials;
        }
        val surrogateAccounts = surrogateAuthenticationService.getImpersonationAccounts(credential.getId(), Optional.empty());
        val surrogateUsername = credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class)
            .map(SurrogateCredentialTrait::getSurrogateUsername)
            .orElseThrow();
        if (!surrogateAccounts.contains(surrogateUsername)) {
            throw new SurrogateAuthenticationException(
                "Unable to authorize surrogate authentication request for " + surrogateUsername);
        }
        return CollectionUtils.wrapList(prepareCredential(request, credential));
    }

    protected @Nullable MutableCredential extractCredential(final HttpServletRequest request,
                                                            final List<Credential> credentials) {
        val credential = (MutableCredential) credentials.getFirst();
        if (credential != null) {
            var surrogateUsername = request.getHeader(REQUEST_HEADER_SURROGATE_PRINCIPAL);
            if (StringUtils.isNotBlank(surrogateUsername)) {
                LOGGER.debug("Request surrogate principal [{}]", surrogateUsername);
                credential.getCredentialMetadata().addTrait(new SurrogateCredentialTrait(surrogateUsername));
                return credential;
            }

            val username = credential.getId();
            val separator = properties.getCore().getSeparator();

View on GitHub (pinned to e7288fc434)