apereo/cas · error · SurrogateAuthenticationException
Unable to authorize surrogate authentication request for
Error message
Unable to authorize surrogate authentication request for
What it means
CAS throws SurrogateAuthenticationException in SurrogateAuthenticationRestHttpRequestCredentialFactory.fromRequest when a REST credential carries a SurrogateCredentialTrait whose surrogateUsername is not among the impersonation accounts the SurrogateAuthenticationService returns for the authenticated principal. CAS only allows a principal to impersonate accounts explicitly authorized (e.g. via surrogate eligible groups/attributes), so an unlisted target username is rejected before any surrogate credential is prepared.
Solutions
- Verify the surrogate username exists and is spelled correctly; it must be an account the principal is allowed to impersonate
- Add the principal to the surrogate eligibility source (surrogateGroups/surrogateAttributes for the simple service, or the LDAP/custom SurrogateAuthenticationService repository)
- If all members of a group may impersonate anyone, use the wildcard separator (e.g. 'user~*') so getImpersonationAccounts returns '*'
- Debug surrogateAuthenticationService.getImpersonationAccounts(principal) to confirm the expected accounts are returned
Example fix
// before cas.authn.surrogate.simple.surrogateGroups= // after cas.authn.surrogate.simple.surrogateGroups=impersonators cas.authn.surrogate.simple.surrogateAttributes=empId
Defensive patterns
Strategy: validation
Validate before calling
var accounts = surrogateAuthenticationService.getImpersonationAccounts(credential.getId(), Optional.empty());
if (trait.map(SurrogateCredentialTrait::getSurrogateUsername).map(u -> !accounts.contains(u)).orElse(true)) {
throw new IllegalArgumentException("surrogate username not authorized for " + credential.getId());
} Try / catch
try {
return factory.fromRequest(request, credential);
} catch (SurrogateAuthenticationException e) {
LOGGER.warn("Surrogate not authorized: {}", e.getMessage());
return CollectionUtils.wrapList(credential); // fall back to principal-only authn
} Prevention
- Always call getImpersonationAccounts() for the principal before constructing the surrogate credential
- Use CAS's own surrogate eligibility REST/API to preview allowed impersonations
- Keep group/attribute eligibility config in sync with the IdP data
- Log and alert on repeated surrogate authorization failures (possible probing)
When it happens
Trigger: A REST client submits credentials with a surrogate trait (principal + surrogate username) whose surrogateUsername is not in surrogateAuthenticationService.getImpersonationAccounts(principalId, empty).
Common situations: Typo in the surrogate username; user not member of the configured surrogate-eligible attribute/group (cas.authn.surrogate.simple.surrogateGroups / surrogateAttributes); using wildcard like '*' when separation pattern is not configured; expecting admin-right impersonation without configuring an eligible surrogate search.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Impersonating is not allowed
- Principal attribute [
- Dn format cannot be empty/blank for authentication
- Token is not authorized for device identifier
- Radius authentication failed for user
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/1b21c5fbf5eb01d1.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/authentication/rest/SurrogateAuthenticationRestHttpRequestCredentialFactory.java:62
}
@Override
public List<Credential> fromRequest(final HttpServletRequest request, final MultiValueMap<String, String> requestBody) throws Throwable {
val credentials = super.fromRequest(request, requestBody);
if (credentials.isEmpty()) {
return credentials;
}
val credential = FunctionUtils.doUnchecked(() -> extractCredential(request, credentials));
if (credential == null) {
LOGGER.trace("Not a surrogate authentication attempt, returning parent class credentials");
return credentials;
}
val surrogateAccounts = surrogateAuthenticationService.getImpersonationAccounts(credential.getId(), Optional.empty());
val surrogateUsername = credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class)
.map(SurrogateCredentialTrait::getSurrogateUsername)
.orElseThrow();
if (!surrogateAccounts.contains(surrogateUsername)) {
throw new SurrogateAuthenticationException(
"Unable to authorize surrogate authentication request for " + surrogateUsername);
}
return CollectionUtils.wrapList(prepareCredential(request, credential));
}
protected @Nullable MutableCredential extractCredential(final HttpServletRequest request,
final List<Credential> credentials) {
val credential = (MutableCredential) credentials.getFirst();
if (credential != null) {
var surrogateUsername = request.getHeader(REQUEST_HEADER_SURROGATE_PRINCIPAL);
if (StringUtils.isNotBlank(surrogateUsername)) {
LOGGER.debug("Request surrogate principal [{}]", surrogateUsername);
credential.getCredentialMetadata().addTrait(new SurrogateCredentialTrait(surrogateUsername));
return credential;
}
val username = credential.getId();
val separator = properties.getCore().getSeparator();View on GitHub (pinned to e7288fc434)