{"record":{"id":"1bd33891b9e9ec3c","repo":"quarkusio/quarkus","slug":"oidc-tenants-s-and-s-share-the-same-back-cha","errorCode":null,"errorMessage":"OIDC tenants '%s' and '%s' share the same back-channel logout path '%s', which is not supported","messagePattern":"OIDC tenants '(.+?)' and '(.+?)' share the same back-channel logout path '(.+?)', which is not supported","errorType":"exception","errorClass":"OIDCException","httpStatus":null,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java","lineNumber":127,"sourceCode":"                String routePath = getTenantLogoutPath(configContext);\n                if (routePath.contains(\"*\")) {\n                    throw new IllegalStateException(\"Back-channel logout path cannot contain a wildcard '*' character\");\n                }\n                OidcTenantConfig previousConfig = pathCache.put(routePath, configContext.oidcConfig());\n                tenantIdCache.add(configContext.oidcConfig().tenantId().get());\n                if (previousConfig == null) {\n                    Handler<RoutingContext> routeHandler = new RouteHandler(configContext, resolver);\n                    builder.addPath(routePath, routeHandler);\n                } else {\n                    String previousTenantId = previousConfig.tenantId().get();\n                    String currentTenantId = configContext.oidcConfig().tenantId().get();\n                    // maybe invalid state, but technically it could happen that some produces a static tenant with\n                    // a same id as a dynamic tenant\n                    if (!previousTenantId.equals(currentTenantId)) {\n                        String errorMessage = \"OIDC tenants '%s' and '%s' share the same back-channel logout path '%s', which is not supported\"\n                                .formatted(previousTenantId, currentTenantId, routePath);\n                        LOG.error(errorMessage);\n                        throw new OIDCException(errorMessage);\n                    }\n                }\n            }\n        }\n        if (builder != null) {\n            pathMatcher = builder.build();\n        } else {\n            pathMatcher = null;\n        }\n        return tenantIdCache;\n    }\n\n    private String getTenantLogoutPath(TenantConfigContext tenant) {\n        return OidcUtils.getRootPath(resolver.getRootPath()) + tenant.oidcConfig().logout().backchannel().path().orElse(null);\n    }\n\n    private static final class RouteHandler implements Handler<RoutingContext> {\n        private final TenantConfigContext tenantContext;","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutHandler.java#L109-L145","documentation":"When two OIDC tenants (static or dynamic) resolve to the same back-channel logout path, the path matcher cannot route a logout callback to a unique tenant. The handler throws OIDCException (after logging an error) unless the duplicate comes from a previous config identical to the current tenant id.","triggerScenarios":"Two tenants whose back-channel logout paths (or default paths) are equal — e.g. tenant-a and tenant-b both configured with the same backchannel-path — while building the path matcher via createPathMatcher/currentTenantIds.","commonSituations":"Copy-pasting tenant config and leaving the same logout path; a dynamic tenant registered at runtime that collides with a static tenant's path.","solutions":["Give each tenant a unique back-channel logout path (e.g. prefix with tenant id)","Remove or merge the duplicate tenant configuration","If a dynamic tenant duplicates a static tenant intentionally, ensure ids match, otherwise fix the path"],"exampleFix":"// before\nquarkus.oidc.tenant-a.logout.backchannel-path=/back-channel-logout\nquarkus.oidc.tenant-b.logout.backchannel-path=/back-channel-logout\n// after\nquarkus.oidc.tenant-a.logout.backchannel-path=/tenant-a/back-channel-logout\nquarkus.oidc.tenant-b.logout.backchannel-path=/tenant-b/back-channel-logout","handlingStrategy":"validation","validationCode":"Set<String> paths = new HashSet<>();\nfor (tenant : allTenants) {\n    String p = tenant.logout().backchannel().path().orElse(DEFAULT);\n    if (!paths.add(p)) throw new IllegalStateException(\"Duplicate back-channel logout path: \" + p);\n}","typeGuard":null,"tryCatchPattern":"try { handler.createPathMatcher(); } catch (OIDCException e) { if (e.getMessage().contains(\"share the same back-channel logout path\")) { /* make paths unique per tenant */ } }","preventionTips":["Prefix logout paths with tenant id in multi-tenant setups","Keep a registry test asserting path uniqueness","Watch startup logs for the LOG.error emitted before the exception"],"tags":["oidc","config","logout","multi-tenancy"],"backgroundTag":"duplicate-logout-path","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}