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
- Align the registered service's tenant attribute with the tenant that issued the ticket
- Re-issue a fresh service ticket against a service definition belonging to the same tenant
- Check the service registry for duplicate/overlapping serviceId patterns across tenant definitions
- 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
- Keep registered service tenant attributes in sync with the issuing tenant
- Remove duplicate/overlapping service definitions across tenants
- Re-request tickets after any tenant reconfiguration
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
- No authentication found for ticket
- Impersonating is not allowed
- Service ticket [ ] issued for service [ ] has already…
- screen.service.error.message
- Service ticket [ ] does not exist.
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)