apereo/cas · error · UnauthorizedServiceException
Unauthorized
Error message
Unauthorized
What it means
RegisteredServiceAccessStrategyAuditableEnforcer evaluates whether an incoming service request is authorized. When the requested service has no matching registered service in the service registry, the auditable result is marked with UnauthorizedServiceException('Unauthorized'). CAS refuses to issue tickets for unregistered services as a core security policy.
Solutions
- Add/register the service in the service registry (e.g. JSON file in the services directory) with a pattern matching the service URL
- Verify the service registry configuration (registry location/connection) and that CAS actually loaded the services (check logs/services admin UI)
- Loosen or correct the serviceId regex so it matches the actual callback URL (test with the regex evaluator)
- Check the service registry is watchable/reload-enabled or restart CAS after adding definitions
Example fix
// before: no matching definition
// {"@class":"org.apereo.cas.services.RegexRegisteredService","serviceId":"^https://old.example.com/.*",...}
// after: pattern matches actual callback
// {"@class":"org.apereo.cas.services.RegexRegisteredService","serviceId":"^https://app.example.com/(.*)","id":1,"name":"App",...} Defensive patterns
Strategy: validation
Validate before calling
Optional<RegisteredService> rs = servicesManager.findServiceBy(service);
if (rs.isEmpty()) {
// service will be denied; register it before redirecting clients
} Type guard
boolean isServiceRegistered(ServicesManager sm, Service s) {
return s != null && sm.findServiceBy(s).isPresent();
} Try / catch
try { enforcer.execute(ctx); } catch (UnauthorizedServiceException e) {
renderUnauthorizedView(ctx.getService()); // friendly error page
} Prevention
- Add service definitions before pointing client apps at CAS
- Use hot-reload/watchable service registry storage
- Test serviceId regexes against exact callback URLs (scheme, path, params)
- Monitor 'Service is not registered' warnings in logs
When it happens
Trigger: execute() runs on an AuditableContext whose service does not match any RegisteredService in the configured service registry (no service definition whose serviceId pattern matches the requested service URL).
Common situations: Fresh CAS deployment with an empty/in-memory registry; service registry source (JSON, YAML, JDBC, Mongo) not loaded or misconfigured path/URL; regex in the service definition does not match the callback URL (trailing slashes, query params, http vs https); registry not reloaded after adding the service.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- [ ] is not found in the registry or service access is…
- Service is not found or is disabled in the service registry.
- Service [ ] is not found in service registry.
- Service Management: Unauthorized Service Access. Service
- No metadata could be found for
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/a72c55ce69444147.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-services-api/src/main/java/org/apereo/cas/services/RegisteredServiceAccessStrategyAuditableEnforcer.java:212
@Audit(action = AuditableActions.SERVICE_ACCESS_ENFORCEMENT,
actionResolverName = AuditActionResolvers.SERVICE_ACCESS_ENFORCEMENT_ACTION_RESOLVER,
resourceResolverName = AuditResourceResolvers.SERVICE_ACCESS_ENFORCEMENT_RESOURCE_RESOLVER)
public AuditableExecutionResult execute(final AuditableContext context) {
return byExternalAccessStrategyEnforcers(context)
.or(() -> byServiceTicketAndAuthnResultAndRegisteredService(context))
.or(() -> byServiceAndRegisteredServiceAndTicketGrantingTicket(context))
.or(() -> byServiceAndRegisteredServiceAndPrincipal(context))
.or(() -> byServiceAndRegisteredServiceAndAuthentication(context))
.or(() -> byServiceAndRegisteredService(context))
.or(() -> byRegisteredService(context))
.orElseGet(() -> {
val result = AuditableExecutionResult.builder()
.registeredService(context.getRegisteredService().orElse(null))
.service(context.getService().orElse(null))
.authentication(context.getAuthentication().orElse(null))
.build();
result.setException(UnauthorizedServiceException.denied("Unauthorized"));
LOGGER.warn("Service is not registered in the service registry. "
+ "Service is [{}] and registered service is [{}]",
result.getService().map(Service::getId).orElse(null),
result.getRegisteredService().map(RegisteredService::getName).orElse(null));
return result;
});
}
protected Optional<AuditableExecutionResult> byExternalAccessStrategyEnforcers(final AuditableContext context) {
val enforcers = applicationContext.getBeansOfType(RegisteredServiceAccessStrategyEnforcer.class).values();
return enforcers
.stream()
.filter(BeanSupplier::isNotProxy)
.sorted(AnnotationAwareOrderComparator.INSTANCE)
.map(Unchecked.function(enforcer -> enforcer.execute(context)))
.filter(Objects::nonNull)
.filter(AuditableExecutionResult::isExecutionFailure)
.findFirst();
}View on GitHub (pinned to e7288fc434)