apereo/cas · error · SurrogateAuthenticationException

Impersonation is not allowed for

Error message

Impersonation is not allowed for [{}]

What it means

SurrogateServiceTicketGeneratorAuthority.shouldGenerate validates, before issuing a service ticket, that any surrogate credential trait present is legitimately allowed: it asks surrogateAuthenticationService.canImpersonate for the resolved principal and surrogate username. If impersonation is denied, it logs this warning and throws SurrogateAuthenticationException, refusing ticket generation. This is a hard authorization failure, unlike the earlier warn-and-return paths.

Solutions

  1. Re-authenticate so eligibility is re-evaluated with current settings/LDAP state.
  2. Restore the principal's eligibility (eligible-accounts map or LDAP member attribute) if denial is unintended.
  3. Ensure the same surrogate authentication service implementation is used for selection and ticket generation.
  4. Log principal.id and surrogateUser at debug to find normalization/casing mismatches.

Example fix

// before
// principal 'jdoe' not in eligible-accounts, ticket generation proceeds and throws
// after
cas.authn.surrogate.simple.eligible-accounts.jdoe=user1  # then re-login as jdoe
Defensive patterns

Strategy: try-catch

Validate before calling

if (!surrogateAuthenticationService.canImpersonate(surrogateUser, principal, service)) {
    throw new SurrogateAuthenticationException("Not allowed"); // fail fast, don't request ticket
}

Type guard

static boolean isSurrogateCredential(Credential c) {
    return c.getCredentialMetadata() != null
        && c.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class).isPresent();
}

Try / catch

try {
    ticket = ticketRegistryGrantor.grantServiceTicket(...);
} catch (SurrogateAuthenticationException e) {
    // re-authenticate or deny the impersonation request
}

Prevention

When it happens

Trigger: A credential carrying SurrogateCredentialTrait(surrogateUser) reaches shouldGenerate, and surrogateAuthenticationService.canImpersonate(surrogateUser, principal, service) returns false (e.g. eligibility removed after authentication, or simple map lacks the principal).

Common situations: User authenticated before their eligibility was revoked, then requests a service ticket; mismatch between the surrogate service backing the webflow selection and the one validating ticket generation; principal id normalization differences causing canImpersonate to see a different id.

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/ceec383d828bf774. Report an issue: GitHub.

Appendix: source

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

        val authentication = authenticationResult.getAuthentication();
        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)