{"record":{"id":"da214c1c6979af9f","repo":"apereo/cas","slug":"unknown-tenant-s-for-service-ticket-s","errorCode":null,"errorMessage":"Unknown tenant %s for service ticket %s","messagePattern":"Unknown tenant (.+?) for service ticket (.+?)","errorType":"exception","errorClass":"UnknownTenantException","httpStatus":null,"severity":"error","filePath":"core/cas-server-core/src/main/java/org/apereo/cas/DefaultCentralAuthenticationService.java","lineNumber":191,"sourceCode":"            LOGGER.debug(\"Resolved service [{}] from the authentication request with service [{}] linked to service ticket [{}]\",\n                resolvedService, selectedService, serviceTicket.getId());\n\n            configurationContext.getLockRepository().execute(serviceTicket.getId(),\n                Unchecked.supplier(() -> {\n                    if (serviceTicket.isExpired()) {\n                        LOGGER.info(\"Service ticket [{}] has expired.\", serviceTicketId);\n                        throw new InvalidTicketException(serviceTicketId);\n                    }\n                    if (!configurationContext.getServiceMatchingStrategy().matches(selectedService, resolvedService)) {\n                        LOGGER.error(\"Service ticket [{}] with service [{}] does not match supplied service [{}]\",\n                            serviceTicketId, serviceTicket.getService().getId(), Objects.requireNonNull(resolvedService).getId());\n                        throw new UnrecognizableServiceForServiceTicketValidationException(selectedService);\n                    }\n                    if (StringUtils.isNotBlank(serviceTicket.getTenantId())) {\n                        if (!Strings.CI.equals(Objects.requireNonNull(resolvedService).getTenant(), serviceTicket.getTenantId())) {\n                            LOGGER.warn(\"Service ticket [{}] is not assigned to the same tenant [{}] as the service [{}]\",\n                                serviceTicketId, serviceTicket.getTenantId(), resolvedService.getId());\n                            throw new UnknownTenantException(\"Unknown tenant %s for service ticket %s\"\n                                .formatted(resolvedService.getTenant(), serviceTicketId));\n                        }\n                        if (configurationContext.getTenantExtractor().getTenantsManager().findTenant(serviceTicket.getTenantId()).isEmpty()) {\n                            LOGGER.warn(\"Service ticket [{}] is not assigned to a known valid tenant [{}] for service [{}]\",\n                                serviceTicketId, serviceTicket.getTenantId(), resolvedService.getId());\n                            throw new UnknownTenantException(\"Unknown tenant %s for service ticket %s\"\n                                .formatted(serviceTicket.getTenantId(), serviceTicketId));\n                        }\n                    }\n\n                    serviceTicket.update();\n                    if (!serviceTicket.isStateless()) {\n                        configurationContext.getTicketRegistry().updateTicket(serviceTicket);\n                    }\n                    return serviceTicket;\n                }));\n\n            val registeredService = configurationContext.getServicesManager().findServiceBy(selectedService);","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/core/cas-server-core/src/main/java/org/apereo/cas/DefaultCentralAuthenticationService.java#L173-L209","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n{\n  \"serviceId\": \"^https://app\\.example\\.org/.*\",\n  \"tenant\": \"tenant-b\"\n}\n// after\n{\n  \"serviceId\": \"^https://app\\.example\\.org/.*\",\n  \"tenant\": \"tenant-a\"\n}","handlingStrategy":"validation","validationCode":"RegisteredService svc = servicesManager.findServiceBy(serviceUrl);\nString ticketTenant = serviceTicket.getTenantId();\nif (ticketTenant != null && !ticketTenant.equalsIgnoreCase(svc.getTenant())) {\n    throw new IllegalStateException(\"Ticket tenant \" + ticketTenant + \" does not match service tenant \" + svc.getTenant());\n}","typeGuard":null,"tryCatchPattern":"try {\n    centralAuthenticationService.validateServiceTicket(ticketId, service);\n} catch (UnknownTenantException e) {\n    logger.error(\"Tenant mismatch for ticket {}; request a ticket from the correct tenant\", e.getMessage());\n}","preventionTips":["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"],"tags":["multitenant","service-ticket","tenant-mismatch"],"backgroundTag":"invalid-identifier","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}