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
- Add the primary user to the target account's surrogate eligibility (e.g. update the surrogateAccounts attribute or the surrogate-eligibility JSON source).
- Verify cas.authn.surrogate.* configuration (json/ldap/groovy search filter) actually returns eligibility for this pair.
- Check attribute resolution: the principal must carry the attributes the eligibility source expects.
- 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
- Maintain the surrogate eligibility source as code-reviewed data
- Validate surrogate usernames against the eligibility source in the UI before submission
- Surface eligibility checks early in the flow, not only at authentication time
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Principal is unauthorized to authenticate as
- [ ] is not eligible to authenticate as [ ]
- Impersonation is not allowed for
- Missing surrogate username in credential
- LDAP response is not found or does not contain a result…
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)