apereo/cas · error · UnauthorizedServiceException
screen.service.error.message
screen.service.error.message
Error message
Unauthorized: %s
What it means
AuthenticationPolicyAwareServiceTicketValidationAuthorizer.authorize throws UnauthorizedServiceException (message 'Unauthorized: <serviceId>') during service ticket validation when the primary authentication record has no SUCCESSFUL_AUTHENTICATION_HANDLERS attribute, so the authorizer cannot verify that the required handlers satisfied the assertion.
Solutions
- Ensure the authentication flow records AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS in primary authentication attributes (default CAS behavior — check for customization).
- Review the registered service's required-handler policy; relax requiredAuthenticationHandlers if handler tracking is not needed.
- Force re-authentication (renew=true) so a fresh authentication record with handler attributes is created.
- Verify all nodes in the cluster run a CAS version that propagates the handler attribute into assertions.
- Check that resolved handlers' names actually match the recorded names (custom handler getName overrides).
Example fix
// before: service policy demands handlers but session lacks them // after: force fresh authentication at login https://cas.example.org/cas/login?service=...&renew=true // or in service registry JSON: "requiredHandlers": []
Defensive patterns
Strategy: try-catch
Validate before calling
// Before relying on handler-based policy, confirm the attribute exists:
var handlers = assertion.getPrimaryAuthentication().getAttributes()
.get(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS);
if (handlers == null || handlers.isEmpty()) {
LOGGER.warn("Assertion lacks successful-handler attributes; validation will be denied");
} Type guard
boolean hasHandlerRecord(PrimaryAuthentication auth) {
return auth.getAttributes().containsKey(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS);
} Try / catch
try {
authorizer.authorize(request, response, assertion, registeredService);
} catch (UnauthorizedServiceException e) {
LOGGER.error("Ticket validation denied for {}: {}", service.getId(), e.getMessage());
// redirect user to re-authenticate with renew=true
} Prevention
- Do not strip/rewrite primary authentication attributes in custom flows or ticket translators.
- Keep handler names in the execution plan consistent with what services require.
- Use renew=true when a stale SSO session may lack handler metadata.
- Keep all cluster nodes on versions that record SUCCESSFUL_AUTHENTICATION_HANDLERS.
When it happens
Trigger: Validating an ST for a service whose registered service policy requires specific authentication handlers, when the assertion's primary authentication attributes lack the successful-handlers attribute — e.g. the ticket was issued by a flow/path that does not record handler names, or assertion data was reconstructed/serialized without it.
Common situations: Ticket issued by an older CAS node or a proxying chain that strips authentication attributes; custom authentication handlers not registering in the execution plan; policy on the service requiring handlers (requiredHandlers) while the SSO session predates that configuration.
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Denied
- Impersonation is not allowed for
- Authentication handler is disabled
- No user can be accepted because none is defined
- not found in backing map.
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/b74618b46bf05838.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-validation-api/src/main/java/org/apereo/cas/validation/AuthenticationPolicyAwareServiceTicketValidationAuthorizer.java:45
@RequiredArgsConstructor
public class AuthenticationPolicyAwareServiceTicketValidationAuthorizer implements ServiceTicketValidationAuthorizer {
private final ServicesManager servicesManager;
private final AuthenticationEventExecutionPlan authenticationEventExecutionPlan;
private final ConfigurableApplicationContext applicationContext;
@Override
public void authorize(final HttpServletRequest request, final Service service, final Assertion assertion) {
val registeredService = servicesManager.findServiceBy(service);
RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(service, registeredService);
LOGGER.debug("Evaluating service [{}] to ensure required authentication handlers can satisfy assertion", service);
val primaryAuthentication = assertion.getPrimaryAuthentication();
val attributes = primaryAuthentication.getAttributes();
if (!attributes.containsKey(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS)) {
LOGGER.warn("No successful authentication handlers are recorded for the authentication attempt");
throw UnauthorizedServiceException.denied("Unauthorized: %s".formatted(service.getId()));
}
val successfulHandlerNames = CollectionUtils.toCollection(attributes.get(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS));
val assertedHandlers = authenticationEventExecutionPlan.resolveAuthenticationHandlers()
.stream()
.filter(BeanSupplier::isNotProxy)
.filter(handler -> successfulHandlerNames.contains(handler.getName()))
.collect(Collectors.toSet());
val policies = authenticationEventExecutionPlan.getAuthenticationPolicies(primaryAuthentication);
policies.forEach(policy -> {
try {
val simpleName = policy.getClass().getSimpleName();
LOGGER.trace("Executing authentication policy [{}]", simpleName);
val result = policy.isSatisfiedBy(primaryAuthentication, assertedHandlers, applicationContext,
Map.of(Assertion.class.getName(), assertion, RegisteredService.class.getName(), registeredService));
if (!result.isSuccess()) {
throw UnauthorizedServiceException.denied("Unauthorized: %s".formatted(service.getId()));
}View on GitHub (pinned to e7288fc434)