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

  1. Add/register the service in the service registry (e.g. JSON file in the services directory) with a pattern matching the service URL
  2. Verify the service registry configuration (registry location/connection) and that CAS actually loaded the services (check logs/services admin UI)
  3. Loosen or correct the serviceId regex so it matches the actual callback URL (test with the regex evaluator)
  4. 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

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.

Related errors


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)