quarkusio/quarkus · error · RuntimeException
Tenant id must have been set by either the session or state
Error message
Tenant id must have been set by either the session or state cookie
What it means
This RuntimeException is thrown by the CustomTenantResolver when the request context already carries a resolved TENANT_ID_ATTRIBUTE but no session or state cookie is present that could have set it. Quarkus OIDC expects an already-resolved tenant id to be traceable to either the OIDC session cookie or the authorization-request state cookie; otherwise the tenant selection is inconsistent. The resolver returns null to signal 'reuse existing tenant context' only in that valid case.
Source
Thrown at integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java:25
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class CustomTenantResolver implements TenantResolver {
@Override
public String resolve(RoutingContext context) {
String path = context.normalizedPath();
// `/hr-classic-perm-check` and '/hr-classic-and-jaxrs-perm-check'
// require policy checks which force an authentication before @Tenant is resolved
if (path.contains("/hr") && !path.contains("/hr-classic-perm-check")
&& !path.contains("/hr-classic-and-jaxrs-perm-check")) {
throw new RuntimeException("@Tenant annotation only must be used to set "
+ "a tenant id on the '" + path + "' request path");
}
if (context.get(OidcUtils.TENANT_ID_ATTRIBUTE) != null) {
if (context.get(OidcUtils.TENANT_ID_SET_BY_SESSION_COOKIE) == null
&& context.get(OidcUtils.TENANT_ID_SET_BY_STATE_COOKIE) == null) {
throw new RuntimeException("Tenant id must have been set by either the session or state cookie");
}
// Expect an already resolved tenant context be used
return null;
}
if (path.contains("recovered-no-discovery")) {
return "no-discovery";
}
if (path.endsWith("code-flow") || path.endsWith("code-flow/logout")) {
return "code-flow";
}
if (path.endsWith("code-flow-form-post") || path.endsWith("code-flow-form-post/front-channel-logout")) {
return "code-flow-form-post";
}
return null;
}
}
View on GitHub (pinned to e1c734241f)
Solutions
- Ensure the client sends the OIDC session and/or state cookies (enable cookie handling in the HTTP client).
- Check that no custom code sets OidcUtils.TENANT_ID_ATTRIBUTE without going through the OIDC session/state cookie path.
- Verify quarkus.oidc.<tenant> cookie/tenant configuration matches the tenant resolver expectations.
- Enable DEBUG logging for io.quarkus.oidc to see which mechanism resolved the tenant id.
Example fix
// before
if (context.get(OidcUtils.TENANT_ID_ATTRIBUTE) != null) {
if (context.get(OidcUtils.TENANT_ID_SET_BY_SESSION_COOKIE) == null
&& context.get(OidcUtils.TENANT_ID_SET_BY_STATE_COOKIE) == null) {
throw new RuntimeException("Tenant id must have been set by either the session or state cookie");
}
return null;
}
// after (client side: keep cookies across the code-flow)
// CookieHandler cookieHandler = new CookieManager();
// HttpClient client = HttpClient.newBuilder().cookieHandler(cookieHandler).build(); Defensive patterns
Strategy: validation
Validate before calling
if (context.get(OidcUtils.TENANT_ID_ATTRIBUTE) != null
&& context.get(OidcUtils.TENANT_ID_SET_BY_SESSION_COOKIE) == null
&& context.get(OidcUtils.TENANT_ID_SET_BY_STATE_COOKIE) == null) {
throw new IllegalStateException("tenant id present but no session/state cookie backing it");
} Type guard
boolean hasCookieBackedTenant(RoutingContext ctx) {
return ctx.get(OidcUtils.TENANT_ID_ATTRIBUTE) != null
&& (ctx.get(OidcUtils.TENANT_ID_SET_BY_SESSION_COOKIE) != null
|| ctx.get(OidcUtils.TENANT_ID_SET_BY_STATE_COOKIE) != null);
} Try / catch
try {
return tenantResolver.resolve(context, tenantResolverFunction);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("session or state cookie")) {
// fall back to default tenant resolution
}
throw e;
} Prevention
- Always keep cookies (session + state) across the OIDC code-flow in test clients.
- Never set OidcUtils.TENANT_ID_ATTRIBUTE from custom code outside the OIDC session/state cookie path.
- Document tenant resolution order for custom multi-tenancy setups.
- Enable io.quarkus.oidc DEBUG logging when changing tenant resolution logic.
When it happens
Trigger: A request hits a path whose tenant id was already resolved and stored in the routing context under OidcUtils.TENANT_ID_ATTRIBUTE, but neither TENANT_ID_SET_BY_SESSION_COOKIE nor TENANT_ID_SET_BY_STATE_COOKIE context attributes are set — e.g. a tenant was injected by another mechanism rather than via OIDC cookies.
Common situations: Misconfigured tenant resolution order, custom filters setting the tenant attribute manually, changes to OIDC session/state cookie naming, or requests replayed without cookies (cookie-less client) hitting tenant-protected endpoints.
Related errors
- Failed to find a matching OidcTenantConfig for tenant:
- OIDC tenants '%s' and '%s' share the same back-channel logou
- Access token expires_in property in the session cookie must
- Tenant configuration has not been resolved
- OidcProviderClient can not be injected
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/2c8456fce3b84d63.
Report an issue: GitHub.