apereo/cas · error · CredentialNotFoundException
Missing surrogate username in credential
Error message
Missing surrogate username in credential
What it means
SurrogateAuthenticationPostProcessor runs after primary authentication to enforce surrogate eligibility. It reads the SurrogateCredentialTrait from the credential metadata; if the trait is present but the surrogate username inside it is blank, it throws CredentialNotFoundException because there is no target user to impersonate.
Solutions
- Correct the submitted surrogate syntax so it contains a non-blank target user (e.g. userA+userB).
- Verify the SurrogateCredentialTrait is populated from the correct request parameter/attribute in the webflow.
- Check any custom credential/trait extraction code for blank-string handling.
- Test with a direct, well-formed surrogate identifier to isolate where the value is lost.
Example fix
// before: malformed surrogate id userB+ // after userA+userB
Defensive patterns
Strategy: validation
Validate before calling
// before invoking the processor, ensure the trait carries a non-blank user
String surrogate = Optional.ofNullable(credential.getCredentialMetadata()
.getTrait(SurrogateCredentialTrait.class))
.map(SurrogateCredentialTrait::getSurrogateUsername)
.orElse("");
if (surrogate.isBlank()) throw new IllegalArgumentException("surrogate username required"); Type guard
function hasSurrogateUsername(cred) {
const trait = cred?.credentialMetadata?.getTrait?.(SurrogateCredentialTrait);
return typeof trait?.surrogateUsername === 'string' && trait.surrogateUsername.trim() !== '';
} Try / catch
try {
processor.process(transaction, result);
} catch (CredentialNotFoundException e) {
// redirect user back to the surrogate selection step
LOGGER.warn("Surrogate username missing from credential");
} Prevention
- Validate the 'userA+userB' syntax in the login/webflow UI
- Unit-test credential trait population for surrogate flows
- Never allow blank surrogate fields to reach authentication post-processing
When it happens
Trigger: A credential was flagged as a surrogate credential (trait present) but extractSurrogateUser produced an empty string — e.g. the 'username+surrogate' syntax was malformed (trailing '+', only '+'), or the webflow submitted an empty surrogate field.
Common situations: Malformed surrogate username in the login form or URL; custom credential wrapping that strips the surrogate part; webflow misconfiguration losing the surrogate parameter before the trait is populated.
Related errors
- Unable to authorize surrogate authentication request for
- Principal is unauthorized to authenticate as
- LDAP response is not found or does not contain a result…
- Attribute [ ] not found or has no values
- [ ] is not eligible to authenticate as [ ]
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/012fd75c911d0329.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/authentication/SurrogateAuthenticationPostProcessor.java:58
val principal = authentication.getPrincipal();
if (!(principal instanceof final SurrogatePrincipal primaryPrincipal)) {
LOGGER.trace("Provided principal is one intended for surrogate authentication");
return;
}
val primaryCredential = transaction.getPrimaryCredential();
if (primaryCredential.isEmpty()) {
throw new AuthenticationException("Unable to determine primary credentials");
}
val surrogateUsername = primaryCredential.get().getCredentialMetadata()
.getTrait(SurrogateCredentialTrait.class)
.map(SurrogateCredentialTrait::getSurrogateUsername)
.orElseThrow(() -> new AuthenticationException("Unable to determine surrogate credential"));
try {
if (StringUtils.isBlank(surrogateUsername)) {
LOGGER.error("No surrogate username was specified as part of the credential");
throw new CredentialNotFoundException("Missing surrogate username in credential");
}
LOGGER.debug("Authenticated [{}] will be checked for surrogate eligibility next for [{}]...", primaryPrincipal, surrogateUsername);
if (transaction.getService() != null) {
val svc = servicesManager.findServiceBy(transaction.getService());
val serviceAccessAudit = AuditableContext.builder()
.service(transaction.getService())
.authentication(authentication)
.registeredService(svc)
.build();
val accessResult = registeredServiceAccessStrategyEnforcer.execute(serviceAccessAudit);
accessResult.throwExceptionIfNeeded();
}
if (surrogateAuthenticationService.canImpersonate(surrogateUsername, primaryPrincipal.getPrimary(), Optional.ofNullable(transaction.getService()))) {
LOGGER.debug("Principal [{}] is authorized to authenticate as [{}]", primaryPrincipal, surrogateUsername);
publishSuccessEvent(primaryPrincipal, surrogateUsername);View on GitHub (pinned to e7288fc434)