apereo/cas · error · SurrogateAuthenticationException

Impersonating is not allowed

Error message

Impersonating %s is not allowed

What it means

SurrogateServiceTicketGeneratorAuthority.shouldGenerate throws SurrogateAuthenticationException when a service ticket is requested on behalf of a surrogate user but surrogateAuthenticationService.canImpersonate(surrogateUser, principal, service) returns false. This is a per-service, per-ticket authorization gate: even if authentication succeeded earlier, ticket generation re-checks impersonation authority and hard-fails if denied.

Solutions

  1. Confirm canImpersonate(target, principal, service) would return true for the exact service URL (e.g. via the surrogate eligibility attributes/groups or LDAP search filter)
  2. Update the surrogate authorization data (group/attribute membership or LDAP filter, including service scoping) so the principal is eligible for that service
  3. Force the user to re-authenticate to refresh the surrogate credential trait after fixing authorization data
  4. Check for stale caches in the SurrogateAuthenticationService and clear them

Example fix

// before
cas.authn.surrogate.ldap.searchFilter=(&(uid={principal})(ssoRole=employee))
// after
cas.authn.surrogate.ldap.searchFilter=(&(uid={surrogate})(member={principal})(ssoRole=employee))
Defensive patterns

Strategy: try-catch

Validate before calling

boolean ok = surrogateAuthenticationService.canImpersonate(surrogateUser, principalId, Optional.ofNullable(service));
if (!ok) throw new IllegalStateException("not allowed to impersonate " + surrogateUser);

Try / catch

try {
    return centralAuthenticationService.grantServiceTicket(tgtId, service, surrogateCredential);
} catch (SurrogateAuthenticationException e) {
    LOGGER.warn("Ticket denied for surrogate: {}", e.getMessage());
    throw e; // rethrow as it is authorization, not transient
}

Prevention

When it happens

Trigger: Requesting a service ticket with a credential whose SurrogateCredentialTrait names a surrogate user that the principal cannot impersonate for the given service (canImpersonate returns false).

Common situations: Surrogate mapping changed after authentication; the service-specific impersonation rules exclude this service; stale cached session still holding a surrogate trait after eligibility was revoked; mismatch between attribute-based eligibility config and the requested service.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/ticket/SurrogateServiceTicketGeneratorAuthority.java:62

        LOGGER.debug("Checking if service ticket generation is allowed for [{}] and [{}]", authentication, service);
        return findSurrogateCredentialTrait(authentication).isPresent();
    }

    @Override
    public boolean shouldGenerate(final AuthenticationResult authenticationResult, final Service service) throws Throwable {
        val authentication = authenticationResult.getAuthentication();
        val result = findSurrogateCredentialTrait(authentication);
        if (result.isPresent()) {
            val pair = result.get();
            val givenService = serviceSelectionPlan.resolveService(service);
            val principal = resolvedPrincipal(pair.getKey().getId());
            val surrogateUser = pair.getRight().getSurrogateUsername();
            LOGGER.debug("Checking if [{}] can impersonate [{}] for service [{}]", principal, surrogateUser, givenService);
            if (surrogateAuthenticationService.canImpersonate(surrogateUser, principal, Optional.ofNullable(givenService))) {
                return true;
            }
            LOGGER.warn("Impersonation is not allowed for [{}]", surrogateUser);
            throw new SurrogateAuthenticationException("Impersonating %s is not allowed".formatted(surrogateUser));
        }
        return true;
    }

    protected Optional<Pair<Credential, SurrogateCredentialTrait>> findSurrogateCredentialTrait(
        final Authentication authentication) {
        return authentication.getCredentials()
            .stream()
            .filter(Objects::nonNull)
            .filter(credential -> Objects.nonNull(credential.getCredentialMetadata()))
            .filter(credential -> credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class).isPresent())
            .map(credential -> {
                val credentialTrait = credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class).orElseThrow();
                return Pair.of(credential, credentialTrait);
            })
            .findFirst();
    }

View on GitHub (pinned to e7288fc434)