5.10.5. Tenant resolution for OIDC web-app applications


Tenant resolution for the OIDC web-app applications must be done at least 3 times during an authorization code flow, when the OIDC tenant-specific configuration affects how each of the following steps is run.

Step 1: Unauthenticated user accesses an endpoint and is redirected to OIDC provider

When an unauthenticated user accesses a secured path, the user is redirected to the OIDC provider to authenticate and the tenant configuration is used to build the redirect URI.

All the static and dynamic tenant resolution options listed in the Static tenant configuration resolution and Dynamic tenant configuration resolution sections can be used to resolve a tenant.

Step 2: The user is redirected back to the endpoint

After the provider authentication, the user is redirected back to the Quarkus endpoint and the tenant configuration is used to complete the authorization code flow.

All the static and dynamic tenant resolution options listed in the Static tenant configuration resolution and Dynamic tenant configuration resolution sections can be used to resolve a tenant. Before the tenant resolution begins, the authorization code flow state cookie is used to set the already resolved tenant configuration id as a RoutingContext tenant-id attribute: both custom dynamic TenantConfigResolver and static TenantResolver tenant resolvers can check it.

Step 3: Authenticated user accesses the secured path using the session cookie

The tenant configuration determines how the session cookie is verified and refreshed. Before the tenant resolution begins, the authorization code flow session cookie is used to set the already resolved tenant configuration id as a RoutingContext tenant-id attribute: both custom dynamic TenantConfigResolver and static TenantResolver tenant resolvers can check it.

For example, here is how a custom TenantConfigResolver can avoid creating the already resolved tenant configuration, that may otherwise require blocking reads from the database or other remote sources:

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.OidcRequestContext;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.ApplicationType;
import io.quarkus.oidc.TenantConfigResolver;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantConfigResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {
        String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
        if (resolvedTenantId != null) { 
1

            return null;
        }

        String path = context.request().path(); 
2

        if (path.endsWith("tenant-a")) {
            return Uni.createFrom().item(createTenantConfig("tenant-a", "client-a", "secret-a"));
        } else if (path.endsWith("tenant-b")) {
            return Uni.createFrom().item(createTenantConfig("tenant-b", "client-b", "secret-b"));
        }

        // Default tenant id
        return null;
    }

    private OidcTenantConfig createTenantConfig(String tenantId, String clientId, String secret) {
        final OidcTenantConfig config = OidcTenantConfig
                .authServerUrl("http://localhost:8180/realms/"  + tenantId)
                .tenantId(tenantId)
                .clientId(clientId)
                .credentials(secret)
                .applicationType(ApplicationType.WEB_APP)
                .build();
        return config;
    }
}
1
Let Quarkus use the already resolved tenant configuration if it has been resolved earlier.
2
Check the request path to create tenant configurations.

The default configuration may look like this:

quarkus.oidc.auth-server-url=http://localhost:8180/realms/default
quarkus.oidc.client-id=client-default
quarkus.oidc.credentials.secret=secret-default
quarkus.oidc.application-type=web-app

The preceding example assumes that the tenant-a, tenant-b and default tenants are all used to protect the same endpoint paths. In other words, after the user has authenticated with the tenant-a configuration, this user will not be able to choose to authenticate with the tenant-b or default configuration before this user logs out and has a session cookie cleared or expired.

The situation where multiple OIDC web-app tenants protect the tenant-specific paths is less typical and also requires an extra care. When multiple OIDC web-app tenants such as tenant-a, tenant-b and default tenants are used to control access to the tenant specific paths, the users authenticated with one OIDC provider must not be able to access the paths requiring an authentication with another provider, otherwise the results can be unpredictable, most likely causing unexpected authentication failures. For example, if the tenant-a authentication requires a Keycloak authentication and the tenant-b authentication requires an Auth0 authentication, then, if the tenant-a authenticated user attempts to access a path secured by the tenant-b configuration, then the session cookie will not be verified, since the Auth0 public verification keys can not be used to verify the tokens signed by Keycloak. An easy, recommended way to avoid multiple web-app tenants conflicting with each other is to set the tenant specific session path as shown in the following example:

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.OidcRequestContext;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.ApplicationType;
import io.quarkus.oidc.TenantConfigResolver;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantConfigResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {
        String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
        if (resolvedTenantId != null) { 
1

            return null;
        }

        String path = context.request().path(); 
2

        if (path.endsWith("tenant-a")) {
            return Uni.createFrom().item(createTenantConfig("tenant-a", "/tenant-a", "client-a", "secret-a"));
        } else if (path.endsWith("tenant-b")) {
            return Uni.createFrom().item(createTenantConfig("tenant-b", "/tenant-b", "client-b", "secret-b"));
        }

        // Default tenant id
        return null;
    }

    private OidcTenantConfig createTenantConfig(String tenantId, String cookiePath, String clientId, String secret) {
        final OidcTenantConfig config = OidcTenantConfig
                .authServerUrl("http://localhost:8180/realms/" + tenantId)
                .tenantId(tenantId)
                .clientId(clientId)
                .credentials(secret)
                .applicationType(ApplicationType.WEB_APP)
                .authentication().cookiePath(cookiePath).end()  
3

                .build();
        return config;
    }
}
1
Let Quarkus use the already resolved tenant configuration if it has been resolved earlier.
2
Check the request path to create tenant configurations.
3
Set the tenant-specific cookie paths which makes sure the session cookie is only visible to the tenant which created it.

The default tenant configuration should be adjusted like this:

quarkus.oidc.auth-server-url=http://localhost:8180/realms/default
quarkus.oidc.client-id=client-default
quarkus.oidc.credentials.secret=secret-default
quarkus.oidc.authentication.cookie-path=/default
quarkus.oidc.application-type=web-app

Having the same session cookie path when multiple OIDC web-app tenants protect the tenant-specific paths is not recommended and should be avoided as it requires even more care from the custom resolvers, for example:

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.OidcRequestContext;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.ApplicationType;
import io.quarkus.oidc.TenantConfigResolver;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantConfigResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {

        String path = context.request().path(); 
1

        if (path.endsWith("tenant-a")) {
            String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
	    if (resolvedTenantId != null) {
	        if ("tenant-a".equals(resolvedTenantId)) { 
2

	            return null;
	        } else {
	           // Require a "tenant-a" authentication
                   context.remove(OidcUtils.TENANT_ID_ATTRIBUTE); 
3

	        }
            }
            return Uni.createFrom().item(createTenantConfig("tenant-a", "client-a", "secret-a"));
        } else if (path.endsWith("tenant-b")) {
            String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
	    if (resolvedTenantId != null) {
	        if ("tenant-b".equals(resolvedTenantId)) { 
4

	            return null;
	        } else {
	            // Require a "tenant-b" authentication
                   context.remove(OidcUtils.TENANT_ID_ATTRIBUTE); 
5

	        }
            }
            return Uni.createFrom().item(createTenantConfig("tenant-b", "client-b", "secret-b"));
        }

        // Set default tenant id
        context.put(OidcUtils.TENANT_ID_ATTRIBUTE, OidcUtils.DEFAULT_TENANT_ID); 
6

        return null;
    }

    private OidcTenantConfig createTenantConfig(String tenantId, String clientId, String secret) {
        final OidcTenantConfig config = OidcTenantConfig
            .authServerUrl("http://localhost:8180/realms/"  + tenantId)
            .tenantId(tenantId)
            .clientId(clientId)
            .credentials(secret)
            .applicationType(ApplicationType.WEB_APP)
            .build();
        return config;
    }
}
1
Check the request path to create tenant configurations.
2 4
Let Quarkus use the already resolved tenant configuration if the already resolved tenant is expected for the current path.
3 5
Remove the tenant-id attribute if the already resolved tenant configuration is not expected for the current path.
6
Use the default tenant for all other paths. It is equivalent to removing the tenant-id attribute.
Red Hat logoGithubredditYoutubeTwitter

詳細情報

試用、購入および販売

コミュニティー

会社概要

Red Hat は、企業がコアとなるデータセンターからネットワークエッジに至るまで、各種プラットフォームや環境全体で作業を簡素化できるように、強化されたソリューションを提供しています。

多様性を受け入れるオープンソースの強化

Red Hat では、コード、ドキュメント、Web プロパティーにおける配慮に欠ける用語の置き換えに取り組んでいます。このような変更は、段階的に実施される予定です。詳細情報: Red Hat ブログ.

Red Hat ドキュメントについて

Legal Notice

Theme

© 2026 Red Hat
トップに戻る