apereo/cas · error · AuthenticationException

Principal is unauthorized to authenticate as

Error message

Principal  is unauthorized to authenticate as 

What it means

After confirming the surrogate username is present, SurrogateAuthenticationPostProcessor checks eligibility via the surrogate eligibility auditable execution. When the primary principal is NOT allowed to authenticate as the surrogate user, it executes an ineligible-access audit and throws AuthenticationException whose message is 'Principal <p> is unauthorized to authenticate as <s>'.

Solutions

  1. Grant eligibility: add the primary principal to the target account's surrogate list in the configured surrogate source.
  2. Verify the surrogate eligibility source (cas.authn.surrogate.* json/ldap/groovy) is reachable and returns data for this pair.
  3. Check registered-service surrogate policy/attributes aren't filtering out eligibility.
  4. Review the audit trail (surrogateEligibilityAuditableExecution) to see exactly which check denied access.

Example fix

// before: LDAP surrogate filter finds nothing
cas.authn.surrogate.ldap.search-filter=(&(uid={principal})(surrogateMember={surrogate}))
// after: also allow members of a surrogate group
cas.authn.surrogate.ldap.search-filter=(&(objectClass=person)(|(uid={principal})(member={principal})))
Defensive patterns

Strategy: validation

Validate before calling

// check eligibility before running the full surrogate flow
boolean eligible = surrogateAuthenticationService.canImpersonate(surrogateUsername, principal, Optional.of(registeredService));
if (!eligible) { throw new AccessDeniedException("Not eligible to authenticate as " + surrogateUsername); }

Try / catch

try {
    processor.process(transaction, result);
} catch (AuthenticationException e) {
    // present 'not permitted to impersonate' message and audit reference
    LOGGER.warn("Surrogate eligibility denied: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Post-processing a surrogate authentication where canImpersonate (or the eligibility source consulted in process()) denies the primary->surrogate mapping, even though the primary credential itself authenticated fine.

Common situations: Primary user missing from the surrogate allow-list for the target; LDAP/JSON eligibility source unreachable so lookup yields nothing; service-specific surrogate policy blocking this registered service; attribute release filtering out the evidence attributes.

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

Appendix: source

Thrown at support/cas-server-support-surrogate-core/src/main/java/org/apereo/cas/authentication/SurrogateAuthenticationPostProcessor.java:100

                    .build();

                surrogateEligibilityAuditableExecution.execute(surrogateEligibleAudit);
                return;
            }
            LOGGER.error("Principal [{}] is unable/unauthorized to authenticate as [{}]", primaryPrincipal, surrogateUsername);
            throw new FailedLoginException();
        } catch (final Exception e) {
            publishFailureEvent(primaryPrincipal, surrogateUsername);
            final Map<String, Throwable> map = CollectionUtils.wrap(getClass().getSimpleName(),
                new SurrogateAuthenticationException("Principal " + primaryPrincipal + " is unauthorized to authenticate as " + surrogateUsername));

            val surrogateIneligibleAudit = AuditableContext.builder()
                .service(transaction.getService())
                .authentication(authentication)
                .build();

            surrogateEligibilityAuditableExecution.execute(surrogateIneligibleAudit);
            throw new AuthenticationException(map);
        }
    }

    @Override
    public boolean supports(final Credential credential) {
        return credential.getCredentialMetadata().getTrait(SurrogateCredentialTrait.class)
            .stream()
            .anyMatch(trait -> StringUtils.isNotBlank(trait.getSurrogateUsername()));
    }

    private void publishFailureEvent(final Principal principal, final String surrogate) {
        val clientInfo = ClientInfoHolder.getClientInfo();
        val event = new CasSurrogateAuthenticationFailureEvent(this, principal, surrogate, clientInfo);
        publishEvent(event);
    }

    private void publishSuccessEvent(final Principal principal, final String surrogate) {
        val clientInfo = ClientInfoHolder.getClientInfo();

View on GitHub (pinned to e7288fc434)