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
- 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)
- Update the surrogate authorization data (group/attribute membership or LDAP filter, including service scoping) so the principal is eligible for that service
- Force the user to re-authenticate to refresh the surrogate credential trait after fixing authorization data
- 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
- Re-check canImpersonate before every ticket request for a surrogate credential
- Include the exact service URL in your eligibility checks to mirror the generator's logic
- Invalidate cached surrogate sessions after changing eligibility attributes
- Prefer wildcard '~*' eligibility only when impersonating anyone is acceptable
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
- Unable to authorize surrogate authentication request for
- Service ticket [ ] issued for service [ ] has already…
- Could not grant service ticket
- Unknown tenant for service ticket
- No authentication found for ticket
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)