Repository: quarkusio/quarkus
Issue: BlockingOperationNotAllowedException is thrown when access database with panache in TenantConfigResolver

### Describe the bug

The tenant configs are saved in database which will be loaded dynamically with `Panache` by `TenantConfigResolver`, but `resolve` is running in `IO thread` and `EntityManager` of `Panache` is expected to run in `worker thread` to not block the `IO` one. Thus the `BlockingOperationNotAllowedException` is thrown. Here is the stacktrace:

```
ERROR [io.ver.ext.web.RoutingContext] (vert.x-eventloop-thread-5) Unhandled exception in router: io.quarkus.runtime.BlockingOperationNotAllowedException: You have attempted to perform a blocking operation on a IO thread. This is not allowed, as blocking the IO thread will cause major performance issues with your application. If you want to perform blocking EntityManager operations make sure you are doing it from a worker thread.
	at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.checkBlocking(TransactionScopedSession.java:111)
	at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.createQuery(TransactionScopedSession.java:342)
	at io.quarkus.hibernate.orm.runtime.session.ForwardingSession.createQuery(ForwardingSession.java:168)
	at io.quarkus.hibernate.orm.runtime.session.ForwardingSession.createQuery(ForwardingSession.java:47)
	at org.hibernate.Session_5b93bee577ae2f8d76647de04cfab36afbf52958_Synthetic_ClientProxy.createQuery(Session_5b93bee577ae2f8d76647de04cfab36afbf52958_Synthetic_ClientProxy.zig:1916)
	at io.quarkus.hibernate.orm.panache.common.runtime.CommonPanacheQueryImpl.createBaseQuery(CommonPanacheQueryImpl.java:339)
	at io.quarkus.hibernate.orm.panache.common.runtime.CommonPanacheQueryImpl.createQuery(CommonPanacheQueryImpl.java:315)
	at io.quarkus.hibernate.orm.panache.common.runtime.CommonPanacheQueryImpl.firstResult(CommonPanacheQueryImpl.java:260)
	at io.quarkus.hibernate.orm.panache.kotlin.runtime.PanacheQueryImpl.firstResult(PanacheQueryImpl.kt:117)
	at ai.bidwise.backend.account.repository.TenantConfigRepositoryImpl.findByTenant(TenantConfigRepository.kt:17)
	at ai.bidwise.backend.account.repository.TenantConfigRepositoryImpl_Subclass.findByTenant$$superforward1(TenantConfigRepositoryImpl_Subclass.zig:1635)
	at ai.bidwise.backend.account.repository.TenantConfigRepositoryImpl_Subclass$$function$$53.apply(TenantConfigRepositoryImpl_Subclass$$function$$53.zig:33)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
	at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
	at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
	at ai.bidwise.backend.account.repository.TenantConfigRepositoryImpl_Subclass.findByTenant(TenantConfigRepositoryImpl_Subclass.zig:2667)
	at ai.bidwise.backend.account.repository.TenantConfigRepositoryImpl_ClientProxy.findByTenant(TenantConfigRepositoryImpl_ClientProxy.zig:1863)
	at ai.bidwise.backend.account.resolver.MultiTenantConfigResolver.resolve(MultiTenantConfigResolver.kt:39)
	at ai.bidwise.backend.account.resolver.MultiTenantConfigResolver_Subclass.resolve$$superforward1(MultiTenantConfigResolver_Subclass.zig:254)
	at ai.bidwise.backend.account.resolver.MultiTenantConfigResolver_Subclass$$function$$9.apply(MultiTenantConfigResolver_Subclass$$function$$9.zig:33)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
	at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
	at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
	at ai.bidwise.backend.account.resolver.MultiTenantConfigResolver_Subclass.resolve(MultiTenantConfigResolver_Subclass.zig:531)
	at ai.bidwise.backend.account.resolver.MultiTenantConfigResolver_ClientProxy.resolve(MultiTenantConfigResolver_ClientProxy.zig:248)
	at io.quarkus.oidc.runtime.DefaultTenantConfigResolver.getDynamicTenantConfig(DefaultTenantConfigResolver.java:147)
	at io.quarkus.oidc.runtime.DefaultTenantConfigResolver.resolveConfig(DefaultTenantConfigResolver.java:65)
	at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_Subclass.resolveConfig$$superforward1(DefaultTenantConfigResolver_Subclass.zig:400)
	at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_Subclass$$function$$4.apply(DefaultTenantConfigResolver_Subclass$$function$$4.zig:33)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
	at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
	at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
	at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_Subclass.resolveConfig(DefaultTenantConfigResolver_Subclass.zig:830)
	at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_ClientProxy.resolveConfig(DefaultTenantConfigResolver_ClientProxy.zig:304)
	at io.quarkus.oidc.runtime.OidcAuthenticationMechanism.resolve(OidcAuthenticationMechanism.java:59)
	at io.quarkus.oidc.runtime.OidcAuthenticationMechanism.authenticate(OidcAuthenticationMechanism.java:40)
	at io.quarkus.oidc.runtime.OidcAuthenticationMechanism_Subclass.authenticate$$superforward1(OidcAuthenticationMechanism_Subclass.zig:334)
	at io.quarkus.oidc.runtime.OidcAuthenticationMechanism_Subclass$$function$$4.apply(OidcAuthenticationMechanism_Subclass$$function$$4.zig:41)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
	at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
	at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
	at io.quarkus.oidc.runtime.OidcAuthenticationMechanism_Subclass.authenticate(OidcAuthenticationMechanism_Subclass.zig:738)
	at io.quarkus.oidc.runtime.OidcAuthenticationMechanism_ClientProxy.authenticate(OidcAuthenticationMechanism_ClientProxy.zig:249)
	at io.quarkus.vertx.http.runtime.security.HttpAuthenticator.attemptAuthentication(HttpAuthenticator.java:109)
	at io.quarkus.vertx.http.runtime.security.HttpAuthenticator_Subclass.attemptAuthentication$$superforward1(HttpAuthenticator_Subclass.zig:241)
	at io.quarkus.vertx.http.runtime.security.HttpAuthenticator_Subclass$$function$$3.apply(HttpAuthenticator_Subclass$$function$$3.zig:33)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
	at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
	at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
	at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
	at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
	at io.quarkus.vertx.http.runtime.security.HttpAuthenticator_Subclass.attemptAuthentication(HttpAuthenticator_Subclass.zig:517)
	at io.quarkus.vertx.http.runtime.security.HttpAuthenticator_ClientProxy.attemptAuthentication(HttpAuthenticator_ClientProxy.zig:186)
	at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$2.handle(HttpSecurityRecorder.java:101)
	at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$2.handle(HttpSecurityRecorder.java:51)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:151)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
	at io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup.handleHotReplacementRequest(VertxHttpHotReplacementSetup.java:49)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:350)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:346)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:151)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
	at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:55)
	at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:37)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$9.handle(VertxHttpRecorder.java:428)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$9.handle(VertxHttpRecorder.java:425)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:150)
	at io.quarkus.vertx.http.runtime.VertxHttpRecorder$1.handle(VertxHttpRecorder.java:132)
	at io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup$3.handle(VertxHttpHotReplacementSetup.java:86)
	at io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup$3.handle(VertxHttpHotReplacementSetup.java:77)
	at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:124)
	at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:54)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:834)
```

### Expected behavior

Reading tenant configs from database should work in `TenantConfigResolver`.

### Actual behavior

`BlockingOperationNotAllowedException` is thrown when access database with panache in `TenantConfigResolver`

### How to Reproduce?

https://github.com/quarkusio/quarkus-quickstarts/tree/main/security-openid-connect-multi-tenancy-quickstart

1. try to read the config from database.

### Output of `uname -a` or `ver`

Darwin C02T81GVG8WL 19.6.0 Darwin Kernel Version 19.6.0: Tue Jun 22 19:49:55 PDT 2021; root:xnu-6153.141.35~1/RELEASE_X86_64 x86_64

### Output of `java -version`

openjdk version "11.0.9.1" 2020-11-04 OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.1.0.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

------------------------------------------------------------ Gradle 6.9 ------------------------------------------------------------  Build time:   2021-05-07 07:28:53 UTC Revision:     afe2e24ababc7b0213ccffff44970aa18035fc0e  Kotlin:       1.4.20 Groovy:       2.5.12 Ant:          Apache Ant(TM) version 1.10.9 compiled on September 27 2020 JVM:          11.0.9.1 (AdoptOpenJDK 11.0.9.1+1) OS:           Mac OS X 10.15.7 x86_64

### Additional information

I think we should change `resolve` method to return `Uni<OidcTenantConfig>` to support this feature.

Changed files:
integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java

diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java
index 06d81b2106f..c0f9143cef2 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TenantConfigResolver.java
@@ -1,5 +1,8 @@
 package io.quarkus.oidc;
 
+import java.util.function.Supplier;
+
+import io.smallrye.mutiny.Uni;
 import io.vertx.ext.web.RoutingContext;
 
 /**
@@ -19,5 +22,32 @@ public interface TenantConfigResolver {
      * @param context the routing context
      * @return the tenant configuration. If {@code null}, indicates that the default configuration/tenant should be chosen
      */
-    OidcTenantConfig resolve(RoutingContext context);
+    @Deprecated
+    default OidcTenantConfig resolve(RoutingContext context) {
+        throw new UnsupportedOperationException("resolve not implemented");
+    }
+
+    /**
+     * Returns a {@link OidcTenantConfig} given a {@code RoutingContext}.
+     *
+     * @param requestContext the routing context
+     * @return the tenant configuration. If the uni resolves to {@code null}, indicates that the default configuration/tenant
+     *         should be chosen
+     */
+    default Uni<OidcTenantConfig> resolve(RoutingContext routingContext, TenantConfigRequestContext requestContext) {
+        return Uni.createFrom().item(resolve(routingContext));
+    }
+
+    /**
+     * A context object that can be used to run blocking tasks
+     * <p>
+     * Blocking config providers should used this context object to run blocking tasks, to prevent excessive and
+     * unnecessary delegation to thread pools
+     */
+    interface TenantConfigRequestContext {
+
+        Uni<OidcTenantConfig> runBlocking(Supplier<OidcTenantConfig> function);
+
+    }
+
 }
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java
index 3cce7802ed7..b823072f937 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java
@@ -1,5 +1,7 @@
 package io.quarkus.oidc.runtime;
 
+import java.util.function.Function;
+
 import io.netty.handler.codec.http.HttpHeaderNames;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.quarkus.oidc.AccessTokenCredential;
@@ -20,13 +22,17 @@ public class BearerAuthenticationMechanism extends AbstractOidcAuthenticationMec
 
     public Uni<SecurityIdentity> authenticate(RoutingContext context,
             IdentityProviderManager identityProviderManager) {
-        String token = extractBearerToken(context, resolver.resolveConfig(context));
-
-        // if a bearer token is provided try to authenticate
-        if (token != null) {
-            return authenticate(identityProviderManager, context, new AccessTokenCredential(token, context));
-        }
-        return Uni.createFrom().nullItem();
+        return resolver.resolveConfig(context).chain(new Function<OidcTenantConfig, Uni<? extends SecurityIdentity>>() {
+            @Override
+            public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcTenantConfig) {
+                String token = extractBearerToken(context, oidcTenantConfig);
+                // if a bearer token is provided try to authenticate
+                if (token != null) {
+                    return authenticate(identityProviderManager, context, new AccessTokenCredential(token, context));
+                }
+                return Uni.createFrom().nullItem();
+            }
+        });
     }
 
     public Uni<ChallengeData> getChallenge(RoutingContext context) {
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
index 31fa15fbf75..0e6b4911dba 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
@@ -80,32 +80,38 @@ public Uni<Boolean> apply(Permission permission) {
 
     public Uni<SecurityIdentity> authenticate(RoutingContext context,
             IdentityProviderManager identityProviderManager) {
+        return resolver.resolveConfig(context).chain(new Function<OidcTenantConfig, Uni<? extends SecurityIdentity>>() {
+            @Override
+            public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcTenantConfig) {
+
+                final Cookie sessionCookie = context.request().getCookie(getSessionCookieName(oidcTenantConfig));
+
+                // if session already established, try to re-authenticate
+                if (sessionCookie != null) {
+                    Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
+                    return resolvedContext.onItem()
+                            .transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
+                                @Override
+                                public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
+                                    return reAuthenticate(sessionCookie, context, identityProviderManager, tenantContext);
+                                }
+                            });
+                }
 
-        final Cookie sessionCookie = context.request().getCookie(getSessionCookieName(resolver.resolveConfig(context)));
-
-        // if session already established, try to re-authenticate
-        if (sessionCookie != null) {
-            Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
-            return resolvedContext.onItem()
-                    .transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
-                        @Override
-                        public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
-                            return reAuthenticate(sessionCookie, context, identityProviderManager, tenantContext);
-                        }
-                    });
-        }
-
-        final String code = context.request().getParam("code");
-        if (code == null) {
-            return Uni.createFrom().optional(Optional.empty());
-        }
+                final String code = context.request().getParam("code");
+                if (code == null) {
+                    return Uni.createFrom().optional(Optional.empty());
+                }
 
-        // start a new session by starting the code flow dance
-        Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
-        return resolvedContext.onItem().transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
-            @Override
-            public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
-                return performCodeFlow(identityProviderManager, context, tenantContext, code);
+                // start a new session by starting the code flow dance
+                Uni<TenantConfigContext> resolvedContext = resolver.resolveContext(context);
+                return resolvedContext.onItem()
+                        .transformToUni(new Function<TenantConfigContext, Uni<? extends SecurityIdentity>>() {
+                            @Override
+                            public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {
+                                return performCodeFlow(identityProviderManager, context, tenantContext, code);
+                            }
+                        });
             }
         });
     }
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java
index 63b944d558f..660b05aa28b 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java
@@ -1,5 +1,9 @@
 package io.quarkus.oidc.runtime;
 
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.event.Event;
@@ -15,7 +19,10 @@
 import io.quarkus.oidc.TenantConfigResolver;
 import io.quarkus.oidc.TenantResolver;
 import io.quarkus.oidc.TokenStateManager;
+import io.quarkus.runtime.BlockingOperationControl;
+import io.quarkus.runtime.ExecutorRecorder;
 import io.smallrye.mutiny.Uni;
+import io.smallrye.mutiny.subscription.UniEmitter;
 import io.vertx.ext.web.RoutingContext;
 
 @ApplicationScoped
@@ -25,7 +32,6 @@ public class DefaultTenantConfigResolver {
     private static final String CURRENT_STATIC_TENANT_ID = "static.tenant.id";
     private static final String CURRENT_STATIC_TENANT_ID_NULL = "static.tenant.id.null";
     private static final String CURRENT_DYNAMIC_TENANT_CONFIG = "dynamic.tenant.config";
-    private static final String CURRENT_DYNAMIC_TENANT_CONFIG_NULL = "dynamic.tenant.config.null";
 
     @Inject
     Instance<TenantResolver> tenantResolver;
@@ -46,6 +52,42 @@ public class DefaultTenantConfigResolver {
     @ConfigProperty(name = "quarkus.http.proxy.enable-forwarded-prefix")
     boolean enableHttpForwardedPrefix;
 
+    private final TenantConfigResolver.TenantConfigRequestContext blockingRequestContext = new TenantConfigResolver.TenantConfigRequestContext() {
+        @Override
+        public Uni<OidcTenantConfig> runBlocking(Supplier<OidcTenantConfig> function) {
+            return Uni.createFrom().deferred(new Supplier<Uni<? extends OidcTenantConfig>>() {
+                @Override
+                public Uni<OidcTenantConfig> get() {
+                    if (BlockingOperationControl.isBlockingAllowed()) {
+                        try {
+                            OidcTenantConfig result = function.get();
+                            return Uni.createFrom().item(result);
+                        } catch (Throwable t) {
+                            return Uni.createFrom().failure(t);
+                        }
+                    } else {
+                        return Uni.createFrom().emitter(new Consumer<UniEmitter<? super OidcTenantConfig>>() {
+                            @Override
+                            public void accept(UniEmitter<? super OidcTenantConfig> uniEmitter) {
+                                ExecutorRecorder.getCurrent().execute(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        try {
+                                            uniEmitter.complete(function.get());
+                                        } catch (Throwable t) {
+                                            uniEmitter.fail(t);
+                                        }
+                                    }
+                                });
+                            }
+                        });
+                    }
+                }
+            });
+        }
+
+    };
+
     private volatile boolean securityEventObserved;
 
     @PostConstruct
@@ -61,38 +103,47 @@ public void verifyResolvers() {
         }
     }
 
-    OidcTenantConfig resolveConfig(RoutingContext context) {
-        OidcTenantConfig tenantConfig = getDynamicTenantConfig(context);
-        if (tenantConfig == null) {
-            TenantConfigContext tenant = getStaticTenantContext(context);
-            if (tenant != null) {
-                tenantConfig = tenant.oidcConfig;
-            }
-        }
-        return tenantConfig;
+    Uni<OidcTenantConfig> resolveConfig(RoutingContext context) {
+        return getDynamicTenantConfig(context)
+                .map(new Function<OidcTenantConfig, OidcTenantConfig>() {
+                    @Override
+                    public OidcTenantConfig apply(OidcTenantConfig tenantConfig) {
+                        if (tenantConfig == null) {
+                            TenantConfigContext tenant = getStaticTenantContext(context);
+                            if (tenant != null) {
+                                tenantConfig = tenant.oidcConfig;
+                            }
+                        }
+                        return tenantConfig;
+                    }
+                });
     }
 
     Uni<TenantConfigContext> resolveContext(RoutingContext context) {
-        Uni<TenantConfigContext> uniTenantContext = getDynamicTenantContext(context);
-        if (uniTenantContext != null) {
-            return uniTenantContext;
-        }
-        TenantConfigContext tenantContext = getStaticTenantContext(context);
-        if (tenantContext != null && !tenantContext.ready) {
-
-            // check if it the connection has already been created            
-            TenantConfigContext readyTenantContext = tenantConfigBean.getDynamicTenantsConfig()
-                    .get(tenantContext.oidcConfig.tenantId.get());
-            if (readyTenantContext == null) {
-                LOG.debugf("Tenant '%s' is not initialized yet, trying to create OIDC connection now",
-                        tenantContext.oidcConfig.tenantId.get());
-                return tenantConfigBean.getTenantConfigContextFactory().apply(tenantContext.oidcConfig);
-            } else {
-                tenantContext = readyTenantContext;
-            }
-        }
+        return getDynamicTenantContext(context).chain(new Function<TenantConfigContext, Uni<? extends TenantConfigContext>>() {
+            @Override
+            public Uni<? extends TenantConfigContext> apply(TenantConfigContext tenantConfigContext) {
+                if (tenantConfigContext != null) {
+                    return Uni.createFrom().item(tenantConfigContext);
+                }
+                TenantConfigContext tenantContext = getStaticTenantContext(context);
+                if (tenantContext != null && !tenantContext.ready) {
+
+                    // check if it the connection has already been created
+                    TenantConfigContext readyTenantContext = tenantConfigBean.getDynamicTenantsConfig()
+                            .get(tenantContext.oidcConfig.tenantId.get());
+                    if (readyTenantContext == null) {
+                        LOG.debugf("Tenant '%s' is not initialized yet, trying to create OIDC connection now",
+                                tenantContext.oidcConfig.tenantId.get());
+                        return tenantConfigBean.getTenantConfigContextFactory().apply(tenantContext.oidcConfig);
+                    } else {
+                        tenantContext = readyTenantContext;
+                    }
+                }
 
-        return Uni.createFrom().item(tenantContext);
+                return Uni.createFrom().item(tenantContext);
+            }
+        });
     }
 
     private TenantConfigContext getStaticTenantContext(RoutingContext context) {
@@ -139,38 +190,40 @@ TokenStateManager getTokenStateManager() {
         return tokenStateManager.get();
     }
 
-    private OidcTenantConfig getDynamicTenantConfig(RoutingContext context) {
-        OidcTenantConfig oidcConfig = null;
+    private Uni<OidcTenantConfig> getDynamicTenantConfig(RoutingContext context) {
         if (tenantConfigResolver.isResolvable()) {
-            oidcConfig = context.get(CURRENT_DYNAMIC_TENANT_CONFIG);
-            if (oidcConfig == null && context.get(CURRENT_DYNAMIC_TENANT_CONFIG_NULL) == null) {
-                oidcConfig = tenantConfigResolver.get().resolve(context);
-                if (oidcConfig != null) {
-                    context.put(CURRENT_DYNAMIC_TENANT_CONFIG, oidcConfig);
-                } else {
-                    context.put(CURRENT_DYNAMIC_TENANT_CONFIG_NULL, true);
+            Uni<OidcTenantConfig> oidcConfig = context.get(CURRENT_DYNAMIC_TENANT_CONFIG);
+            if (oidcConfig == null) {
+                oidcConfig = tenantConfigResolver.get().resolve(context, blockingRequestContext).memoize().indefinitely();
+                if (oidcConfig == null) {
+                    //shouldn't happen, but guard against it anyway
+                    oidcConfig = Uni.createFrom().nullItem();
                 }
+                context.put(CURRENT_DYNAMIC_TENANT_CONFIG, oidcConfig);
             }
+            return oidcConfig;
         }
-        return oidcConfig;
+        return Uni.createFrom().nullItem();
     }
 
     private Uni<TenantConfigContext> getDynamicTenantContext(RoutingContext context) {
 
-        OidcTenantConfig tenantConfig = getDynamicTenantConfig(context);
-        if (tenantConfig != null) {
-            String tenantId = tenantConfig.getTenantId()
-                    .orElseThrow(() -> new OIDCException("Tenant configuration must have tenant id"));
-            TenantConfigContext tenantContext = tenantConfigBean.getDynamicTenantsConfig().get(tenantId);
-
-            if (tenantContext == null) {
-                return tenantConfigBean.getTenantConfigContextFactory().apply(tenantConfig);
-            } else {
-                return Uni.createFrom().item(tenantContext);
+        return getDynamicTenantConfig(context).chain(new Function<OidcTenantConfig, Uni<? extends TenantConfigContext>>() {
+            @Override
+            public Uni<? extends TenantConfigContext> apply(OidcTenantConfig tenantConfig) {
+                if (tenantConfig != null) {
+                    String tenantId = tenantConfig.getTenantId()
+                            .orElseThrow(() -> new OIDCException("Tenant configuration must have tenant id"));
+                    TenantConfigContext tenantContext = tenantConfigBean.getDynamicTenantsConfig().get(tenantId);
+                    if (tenantContext == null) {
+                        return tenantConfigBean.getTenantConfigContextFactory().apply(tenantConfig);
+                    } else {
+                        return Uni.createFrom().item(tenantContext);
+                    }
+                }
+                return Uni.createFrom().nullItem();
             }
-        }
-
-        return null;
+        });
     }
 
     boolean isEnableHttpForwardedPrefix() {
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java
index 1fdcbe9a246..78e90f34909 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcAuthenticationMechanism.java
@@ -2,6 +2,7 @@
 
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.Function;
 
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
@@ -38,30 +39,42 @@ public void init() {
     @Override
     public Uni<SecurityIdentity> authenticate(RoutingContext context,
             IdentityProviderManager identityProviderManager) {
-        OidcTenantConfig oidcConfig = resolve(context);
-        if (oidcConfig.tenantEnabled == false) {
-            return Uni.createFrom().nullItem();
-        }
-        return isWebApp(context, oidcConfig) ? codeAuth.authenticate(context, identityProviderManager)
-                : bearerAuth.authenticate(context, identityProviderManager);
+        return resolve(context).chain(new Function<OidcTenantConfig, Uni<? extends SecurityIdentity>>() {
+            @Override
+            public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcConfig) {
+                if (!oidcConfig.tenantEnabled) {
+                    return Uni.createFrom().nullItem();
+                }
+                return isWebApp(context, oidcConfig) ? codeAuth.authenticate(context, identityProviderManager)
+                        : bearerAuth.authenticate(context, identityProviderManager);
+            }
+        });
     }
 
     @Override
     public Uni<ChallengeData> getChallenge(RoutingContext context) {
-        OidcTenantConfig oidcConfig = resolve(context);
-        if (oidcConfig.tenantEnabled == false) {
-            return Uni.createFrom().nullItem();
-        }
-        return isWebApp(context, oidcConfig) ? codeAuth.getChallenge(context)
-                : bearerAuth.getChallenge(context);
+        return resolve(context).chain(new Function<OidcTenantConfig, Uni<? extends ChallengeData>>() {
+            @Override
+            public Uni<? extends ChallengeData> apply(OidcTenantConfig oidcTenantConfig) {
+                if (!oidcTenantConfig.tenantEnabled) {
+                    return Uni.createFrom().nullItem();
+                }
+                return isWebApp(context, oidcTenantConfig) ? codeAuth.getChallenge(context)
+                        : bearerAuth.getChallenge(context);
+            }
+        });
     }
 
-    private OidcTenantConfig resolve(RoutingContext context) {
-        OidcTenantConfig oidcConfig = resolver.resolveConfig(context);
-        if (oidcConfig == null) {
-            throw new OIDCException("Tenant configuration has not been resolved");
-        }
-        return oidcConfig;
+    private Uni<OidcTenantConfig> resolve(RoutingContext context) {
+        return resolver.resolveConfig(context).map(new Function<OidcTenantConfig, OidcTenantConfig>() {
+            @Override
+            public OidcTenantConfig apply(OidcTenantConfig oidcTenantConfig) {
+                if (oidcTenantConfig == null) {
+                    throw new OIDCException("Tenant configuration has not been resolved");
+                }
+                return oidcTenantConfig;
+            };
+        });
     }
 
     private boolean isWebApp(RoutingContext context, OidcTenantConfig oidcConfig) {
diff --git a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java
index b121053fdee..46719187af0 100644
--- a/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java
+++ b/integration-tests/oidc-tenancy/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java
@@ -1,94 +1,104 @@
 package io.quarkus.it.keycloak;
 
+import java.util.function.Supplier;
+
 import javax.enterprise.context.ApplicationScoped;
 
 import io.quarkus.oidc.OidcTenantConfig;
 import io.quarkus.oidc.OidcTenantConfig.ApplicationType;
 import io.quarkus.oidc.OidcTenantConfig.Roles.Source;
 import io.quarkus.oidc.TenantConfigResolver;
+import io.smallrye.mutiny.Uni;
 import io.vertx.ext.web.RoutingContext;
 
 @ApplicationScoped
 public class CustomTenantConfigResolver implements TenantConfigResolver {
+
     @Override
-    public OidcTenantConfig resolve(RoutingContext context) {
-        // Make sure this resolver is called only once during a given request
-        if (context.get("dynamic_config_resolved") != null) {
-            throw new RuntimeException();
-        }
-        context.put("dynamic_config_resolved", "true");
+    public Uni<OidcTenantConfig> resolve(RoutingContext context, TenantConfigRequestContext requestContext) {
+        return requestContext.runBlocking(new Supplier<OidcTenantConfig>() {
+            @Override
+            public OidcTenantConfig get() {
+
+                // Make sure this resolver is called only once during a given request
+                if (context.get("dynamic_config_resolved") != null) {
+                    throw new RuntimeException();
+                }
+                context.put("dynamic_config_resolved", "true");
 
-        String path = context.request().path();
-        String tenantId = path.split("/")[2];
-        if ("tenant-d".equals(tenantId)) {
-            OidcTenantConfig config = new OidcTenantConfig();
-            config.setTenantId("tenant-d");
-            config.setAuthServerUrl(getIssuerUrl() + "/realms/quarkus-d");
-            config.setClientId("quarkus-d");
-            config.getCredentials().setSecret("secret");
-            config.getToken().setIssuer(getIssuerUrl() + "/realms/quarkus-d");
-            config.getAuthentication().setUserInfoRequired(true);
-            return config;
-        } else if ("tenant-oidc".equals(tenantId)) {
-            OidcTenantConfig config = new OidcTenantConfig();
-            config.setTenantId("tenant-oidc");
-            String uri = context.request().absoluteURI();
-            String authServerUri = path.contains("tenant-opaque")
-                    ? uri.replace("/tenant-opaque/tenant-oidc/api/user", "/oidc")
-                    : uri.replace("/tenant/tenant-oidc/api/user", "/oidc");
-            config.setAuthServerUrl(authServerUri);
-            config.setClientId("client");
-            return config;
-        } else if ("tenant-oidc-no-discovery".equals(tenantId)) {
-            OidcTenantConfig config = new OidcTenantConfig();
-            config.setTenantId("tenant-oidc-no-discovery");
-            String uri = context.request().absoluteURI();
-            String authServerUri = uri.replace("/tenant/tenant-oidc-no-discovery/api/user", "/oidc");
-            config.setAuthServerUrl(authServerUri);
-            config.setDiscoveryEnabled(false);
-            config.setJwksPath("jwks");
-            config.setClientId("client");
-            return config;
-        } else if ("tenant-oidc-no-introspection".equals(tenantId)) {
-            OidcTenantConfig config = new OidcTenantConfig();
-            config.setTenantId("tenant-oidc-no-introspection");
-            String uri = context.request().absoluteURI();
-            String authServerUri = uri.replace("/tenant/tenant-oidc-no-introspection/api/user", "/oidc");
-            config.setAuthServerUrl(authServerUri);
-            config.token.setAllowJwtIntrospection(false);
-            config.setClientId("client");
-            return config;
-        } else if ("tenant-oidc-introspection-only".equals(tenantId)) {
-            OidcTenantConfig config = new OidcTenantConfig();
-            config.setTenantId("tenant-oidc-introspection-only");
-            String uri = context.request().absoluteURI();
-            String authServerUri = uri.replace("/tenant/tenant-oidc-introspection-only/api/user", "/oidc");
-            config.setAuthServerUrl(authServerUri);
-            config.setDiscoveryEnabled(false);
-            config.setIntrospectionPath("introspect");
-            config.setClientId("client");
-            return config;
-        } else if ("tenant-oidc-no-opaque-token".equals(tenantId)) {
-            OidcTenantConfig config = new OidcTenantConfig();
-            config.setTenantId("tenant-oidc-no-opaque-token");
-            String uri = context.request().absoluteURI();
-            String authServerUri = uri.replace("/tenant-opaque/tenant-oidc-no-opaque-token/api/user", "/oidc");
-            config.setAuthServerUrl(authServerUri);
-            config.token.setAllowOpaqueTokenIntrospection(false);
-            config.setClientId("client");
-            return config;
-        } else if ("tenant-web-app-dynamic".equals(tenantId)) {
-            OidcTenantConfig config = new OidcTenantConfig();
-            config.setTenantId("tenant-web-app-dynamic");
-            config.setAuthServerUrl(getIssuerUrl() + "/realms/quarkus-webapp");
-            config.setClientId("quarkus-app-webapp");
-            config.getCredentials().setSecret("secret");
-            config.getAuthentication().setUserInfoRequired(true);
-            config.getRoles().setSource(Source.userinfo);
-            config.setApplicationType(ApplicationType.WEB_APP);
-            return config;
-        }
-        return null;
+                String path = context.request().path();
+                String tenantId = path.split("/")[2];
+                if ("tenant-d".equals(tenantId)) {
+                    OidcTenantConfig config = new OidcTenantConfig();
+                    config.setTenantId("tenant-d");
+                    config.setAuthServerUrl(getIssuerUrl() + "/realms/quarkus-d");
+                    config.setClientId("quarkus-d");
+                    config.getCredentials().setSecret("secret");
+                    config.getToken().setIssuer(getIssuerUrl() + "/realms/quarkus-d");
+                    config.getAuthentication().setUserInfoRequired(true);
+                    return config;
+                } else if ("tenant-oidc".equals(tenantId)) {
+                    OidcTenantConfig config = new OidcTenantConfig();
+                    config.setTenantId("tenant-oidc");
+                    String uri = context.request().absoluteURI();
+                    String authServerUri = path.contains("tenant-opaque")
+                            ? uri.replace("/tenant-opaque/tenant-oidc/api/user", "/oidc")
+                            : uri.replace("/tenant/tenant-oidc/api/user", "/oidc");
+                    config.setAuthServerUrl(authServerUri);
+                    config.setClientId("client");
+                    return config;
+                } else if ("tenant-oidc-no-discovery".equals(tenantId)) {
+                    OidcTenantConfig config = new OidcTenantConfig();
+                    config.setTenantId("tenant-oidc-no-discovery");
+                    String uri = context.request().absoluteURI();
+                    String authServerUri = uri.replace("/tenant/tenant-oidc-no-discovery/api/user", "/oidc");
+                    config.setAuthServerUrl(authServerUri);
+                    config.setDiscoveryEnabled(false);
+                    config.setJwksPath("jwks");
+                    config.setClientId("client");
+                    return config;
+                } else if ("tenant-oidc-no-introspection".equals(tenantId)) {
+                    OidcTenantConfig config = new OidcTenantConfig();
+                    config.setTenantId("tenant-oidc-no-introspection");
+                    String uri = context.request().absoluteURI();
+                    String authServerUri = uri.replace("/tenant/tenant-oidc-no-introspection/api/user", "/oidc");
+                    config.setAuthServerUrl(authServerUri);
+                    config.token.setAllowJwtIntrospection(false);
+                    config.setClientId("client");
+                    return config;
+                } else if ("tenant-oidc-introspection-only".equals(tenantId)) {
+                    OidcTenantConfig config = new OidcTenantConfig();
+                    config.setTenantId("tenant-oidc-introspection-only");
+                    String uri = context.request().absoluteURI();
+                    String authServerUri = uri.replace("/tenant/tenant-oidc-introspection-only/api/user", "/oidc");
+                    config.setAuthServerUrl(authServerUri);
+                    config.setDiscoveryEnabled(false);
+                    config.setIntrospectionPath("introspect");
+                    config.setClientId("client");
+                    return config;
+                } else if ("tenant-oidc-no-opaque-token".equals(tenantId)) {
+                    OidcTenantConfig config = new OidcTenantConfig();
+                    config.setTenantId("tenant-oidc-no-opaque-token");
+                    String uri = context.request().absoluteURI();
+                    String authServerUri = uri.replace("/tenant-opaque/tenant-oidc-no-opaque-token/api/user", "/oidc");
+                    config.setAuthServerUrl(authServerUri);
+                    config.token.setAllowOpaqueTokenIntrospection(false);
+                    config.setClientId("client");
+                    return config;
+                } else if ("tenant-web-app-dynamic".equals(tenantId)) {
+                    OidcTenantConfig config = new OidcTenantConfig();
+                    config.setTenantId("tenant-web-app-dynamic");
+                    config.setAuthServerUrl(getIssuerUrl() + "/realms/quarkus-webapp");
+                    config.setClientId("quarkus-app-webapp");
+                    config.getCredentials().setSecret("secret");
+                    config.getAuthentication().setUserInfoRequired(true);
+                    config.getRoles().setSource(Source.userinfo);
+                    config.setApplicationType(ApplicationType.WEB_APP);
+                    return config;
+                }
+                return null;
+            }
+        });
     }
 
     private String getIssuerUrl() {