apereo/cas · error · UnknownTenantException

Unknown tenant for service ticket

Error message

Unknown tenant %s for service ticket %s

What it means

During validateServiceTicket, if the ServiceTicket carries a tenantId, CAS checks it against the tenant resolved for the registered service. When Strings.CI.equals shows a mismatch, it logs a warning and throws UnknownTenantException stating the service's tenant and the ticket id.

Solutions

  1. Align the registered service's tenant attribute with the tenant that issued the ticket
  2. Re-issue a fresh service ticket against a service definition belonging to the same tenant
  3. Check the service registry for duplicate/overlapping serviceId patterns across tenant definitions
  4. Verify cas.multitenant configuration and the tenantExtractor setup so resolution picks the right tenant

Example fix

// before
{
  "serviceId": "^https://app\.example\.org/.*",
  "tenant": "tenant-b"
}
// after
{
  "serviceId": "^https://app\.example\.org/.*",
  "tenant": "tenant-a"
}
Defensive patterns

Strategy: validation

Validate before calling

RegisteredService svc = servicesManager.findServiceBy(serviceUrl);
String ticketTenant = serviceTicket.getTenantId();
if (ticketTenant != null && !ticketTenant.equalsIgnoreCase(svc.getTenant())) {
    throw new IllegalStateException("Ticket tenant " + ticketTenant + " does not match service tenant " + svc.getTenant());
}

Try / catch

try {
    centralAuthenticationService.validateServiceTicket(ticketId, service);
} catch (UnknownTenantException e) {
    logger.error("Tenant mismatch for ticket {}; request a ticket from the correct tenant", e.getMessage());
}

Prevention

When it happens

Trigger: Presenting a service ticket whose ticket.getTenantId() differs from resolvedService.getTenant(); e.g. ticket issued under cas.multitenant tenant A but validated against a registered service mapped to tenant B, or the service registry entry has no/blank tenant while the ticket has one.

Common situations: Multitenant deployments where services were migrated between tenants; the requesting service URL matches a service definition from another tenant; stale tickets after tenant reconfiguration; duplicated service patterns across tenants.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/da214c1c6979af9f. Report an issue: GitHub.

Appendix: source

Thrown at core/cas-server-core/src/main/java/org/apereo/cas/DefaultCentralAuthenticationService.java:191

            LOGGER.debug("Resolved service [{}] from the authentication request with service [{}] linked to service ticket [{}]",
                resolvedService, selectedService, serviceTicket.getId());

            configurationContext.getLockRepository().execute(serviceTicket.getId(),
                Unchecked.supplier(() -> {
                    if (serviceTicket.isExpired()) {
                        LOGGER.info("Service ticket [{}] has expired.", serviceTicketId);
                        throw new InvalidTicketException(serviceTicketId);
                    }
                    if (!configurationContext.getServiceMatchingStrategy().matches(selectedService, resolvedService)) {
                        LOGGER.error("Service ticket [{}] with service [{}] does not match supplied service [{}]",
                            serviceTicketId, serviceTicket.getService().getId(), Objects.requireNonNull(resolvedService).getId());
                        throw new UnrecognizableServiceForServiceTicketValidationException(selectedService);
                    }
                    if (StringUtils.isNotBlank(serviceTicket.getTenantId())) {
                        if (!Strings.CI.equals(Objects.requireNonNull(resolvedService).getTenant(), serviceTicket.getTenantId())) {
                            LOGGER.warn("Service ticket [{}] is not assigned to the same tenant [{}] as the service [{}]",
                                serviceTicketId, serviceTicket.getTenantId(), resolvedService.getId());
                            throw new UnknownTenantException("Unknown tenant %s for service ticket %s"
                                .formatted(resolvedService.getTenant(), serviceTicketId));
                        }
                        if (configurationContext.getTenantExtractor().getTenantsManager().findTenant(serviceTicket.getTenantId()).isEmpty()) {
                            LOGGER.warn("Service ticket [{}] is not assigned to a known valid tenant [{}] for service [{}]",
                                serviceTicketId, serviceTicket.getTenantId(), resolvedService.getId());
                            throw new UnknownTenantException("Unknown tenant %s for service ticket %s"
                                .formatted(serviceTicket.getTenantId(), serviceTicketId));
                        }
                    }

                    serviceTicket.update();
                    if (!serviceTicket.isStateless()) {
                        configurationContext.getTicketRegistry().updateTicket(serviceTicket);
                    }
                    return serviceTicket;
                }));

            val registeredService = configurationContext.getServicesManager().findServiceBy(selectedService);

View on GitHub (pinned to e7288fc434)