apereo/cas · error · SurrogateAuthenticationException

Unable to authorize surrogate authentication request for

Error message

Unable to authorize surrogate authentication request for 

What it means

DefaultSurrogateAuthenticationPrincipalBuilder builds an authentication result for impersonation (surrogate) logins. Before building, it asks SurrogateAuthenticationService.canImpersonate() whether the authenticated principal may act as the requested surrogate user; if not, SurrogateAuthenticationException is thrown and the impersonation is denied.

Solutions

  1. Add the primary user to the target account's surrogate eligibility (e.g. update the surrogateAccounts attribute or the surrogate-eligibility JSON source).
  2. Verify cas.authn.surrogate.* configuration (json/ldap/groovy search filter) actually returns eligibility for this pair.
  3. Check attribute resolution: the principal must carry the attributes the eligibility source expects.
  4. Confirm the surrogate username spelling/case matches the stored record.

Example fix

// before: surrogate JSON does not list userA
{"targetUid":"userB","surrogates":["userC"]}
// after
{"targetUid":"userB","surrogates":["userC","userA"]}
Defensive patterns

Strategy: validation

Validate before calling

// before attempting impersonation, check eligibility
boolean can = surrogateAuthenticationService.canImpersonate(surrogateUsername, principal, Optional.empty());
if (!can) { throw new AccessDeniedException("Not eligible to impersonate " + surrogateUsername); }

Try / catch

try {
    return builder.buildSurrogateAuthenticationResult(...);
} catch (SurrogateAuthenticationException e) {
    // show 'not authorized to act as this user' UI, not a generic auth failure
    return Optional.empty();
}

Prevention

When it happens

Trigger: Authenticating as 'userB' with username 'userA+userB' style credentials when userA is not listed in userB's surrogate eligibility (surrogateAccounts attribute or configured eligible sources), with initialAuthentication present and service access already checked.

Common situations: User not present in the surrogate-enabled accounts source (JSON/LDAP/Groovy); surrogate eligibility attribute missing on the principal; policy restricting surrogates to specific services; typos in the surrogate username.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/authentication/DefaultSurrogateAuthenticationPrincipalBuilder.java:82

        val surrogatePrincipal = principalFactory.createPrincipal(surrogate, attributes);
        LOGGER.debug("Built surrogate principal [{}] with primary principal [{}]", surrogatePrincipal, primaryPrincipal);
        return new SurrogatePrincipal(primaryPrincipal, Objects.requireNonNull(surrogatePrincipal));
    }


    @Override
    public Optional<AuthenticationResultBuilder> buildSurrogateAuthenticationResult(
        final AuthenticationResultBuilder authenticationResultBuilder,
        final Credential mutableCredential,
        @Nullable final RegisteredService registeredService) throws Throwable {
        val initialAuthentication = authenticationResultBuilder.getInitialAuthentication();
        if (initialAuthentication.isPresent()) {
            val authentication = initialAuthentication.get();
            val principal = extractPrimaryPrincipal(authentication);

            val surrogateUsername = extractSurrogateUser(mutableCredential);
            if (!surrogateAuthenticationService.canImpersonate(surrogateUsername, principal, Optional.empty())) {
                throw new SurrogateAuthenticationException("Unable to authorize surrogate authentication request for " + surrogateUsername);
            }
            val surrogatePrincipal = buildSurrogatePrincipal(mutableCredential, principal, registeredService);
            val authenticationBuilder = DefaultAuthenticationBuilder.newInstance(authentication).setPrincipal(surrogatePrincipal);
            surrogateAuthenticationService.collectSurrogateAttributes(authenticationBuilder, surrogateUsername, principal.getId());
            return Optional.of(authenticationResultBuilder.collect(authenticationBuilder.build()));
        }
        return Optional.empty();
    }

    protected Principal extractPrimaryPrincipal(final Authentication authentication) {
        return authentication.getPrincipal() instanceof final SurrogatePrincipal surrogatePrincipal
            ? surrogatePrincipal.getPrimary()
            : authentication.getPrincipal();
    }

    protected String extractSurrogateUser(final Credential mutableCredential) {
        return mutableCredential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class)
            .map(SurrogateCredentialTrait::getSurrogateUsername)

View on GitHub (pinned to e7288fc434)