Repository: quarkusio/quarkus
Issue: OIDC 1.10.0.CR1 - event loop thread blocked

**Describe the bug**
Upgraded an existing app from 1.9.2.Final to 1.10.0.CR and I am getting below error

```java
2020-11-12 01:14:14,178 INFO  [io.quarkus] (main) Installed features: [amazon-s3, cache, cdi, kubernetes, logging-gelf, mutiny, oidc, rest-client, resteasy, resteasy-jsonb, resteasy-mutiny, security, smallrye-context-propagation]
2020-11-12 01:17:13,262 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (vert.x-eventloop-thread-0) HTTP Request to /s3/s3.html?state=ea63119a-310d-4c65-9c16-5aded25683ec&session_state=9616cbb2-8869-42e1-ad81-1964a72ee7ff&code=69ad933f-9481-45f6-b731-28960124e1f5.9616cbb2-8869-42e1-ad81-1964a72ee7ff.91b23959-4d87-4fbe-a790-69b838fbe4a0 failed, error id: 479eb3ed-e9db-49e0-a817-28a3d16c0eb0-1: 
java.lang.IllegalStateException: The current thread cannot be blocked: vert.x-eventloop-thread-0
	at io.smallrye.mutiny.operators.UniBlockingAwait.await(UniBlockingAwait.java:29)
	at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:61)
	at io.smallrye.mutiny.groups.UniAwait.indefinitely(UniAwait.java:42)
	at io.quarkus.oidc.runtime.OidcIdentityProvider.verifyCodeFlowAccessToken(OidcIdentityProvider.java:243)
	at io.quarkus.oidc.runtime.OidcIdentityProvider.validateTokenWithOidcServer(OidcIdentityProvider.java:96)
	at io.quarkus.oidc.runtime.OidcIdentityProvider.authenticate(OidcIdentityProvider.java:84)
	at io.quarkus.oidc.runtime.OidcIdentityProvider.access$100(OidcIdentityProvider.java:37)
	at io.quarkus.oidc.runtime.OidcIdentityProvider$1$1.get(OidcIdentityProvider.java:71)
	at io.quarkus.oidc.runtime.OidcIdentityProvider$1$1.get(OidcIdentityProvider.java:59)
	at io.smallrye.mutiny.operators.UniCreateFromDeferredSupplier.subscribing(UniCreateFromDeferredSupplier.java:24)
	at io.smallrye.mutiny.operators.UniSerializedSubscriber.subscribe(UniSerializedSubscriber.java:54)
	at io.smallrye.mutiny.operators.UniSerializedSubscriber.subscribe(UniSerializedSubscriber.java:49)
	at io.smallrye.mutiny.operators.AbstractUni.subscribe(AbstractUni.java:30)
	at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$2.lambda$subscribing$0(ContextPropagationUniInterceptor.java:48)
	at io.smallrye.context.SmallRyeThreadContext.lambda$withContext$1(SmallRyeThreadContext.java:530)
	at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$2.subscribing(ContextPropagationUniInterceptor.java:48)
	at io.smallrye.mutiny.operators.UniSerializedSubscriber.subscribe(UniSerializedSubscriber.java:54)
	at io.smallrye.mutiny.operators.UniSerializedSubscriber.subscribe(UniSerializedSubscriber.java:49)
	at io.smallrye.mutiny.operators.AbstractUni.subscribe(AbstractUni.java:30)
	at io.smallrye.mutiny.operators.UniOnItemTransformToUni.handleInnerSubscription(UniOnItemTransformToUni.java:57)
	at io.smallrye.mutiny.operators.UniOnItemTransformToUni.invokeAndSubstitute(UniOnItemTransformToUni.java:43)
	at io.smallrye.mutiny.operators.UniOnItemTransformToUni$2.onItem(UniOnItemTransformToUni.java:74)
	at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.lambda$onItem$1(ContextPropagationUniInterceptor.java:32)
	at io.smallrye.context.SmallRyeThreadContext.lambda$withContext$1(SmallRyeThreadContext.java:530)
	at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.onItem(ContextPropagationUniInterceptor.java:32)
	at io.smallrye.mutiny.operators.UniSerializedSubscriber.onItem(UniSerializedSubscriber.java:86)
	at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.lambda$onItem$1(ContextPropagationUniInterceptor.java:32)
	at io.smallrye.context.SmallRyeThreadContext.lambda$withContext$1(SmallRyeThreadContext.java:530)
	at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.onItem(ContextPropagationUniInterceptor.java:32)
	at io.smallrye.mutiny.operators.UniSerializedSubscriber.onItem(UniSerializedSubscriber.java:86)
```

**Expected behavior**
OIDC hybrid auth should work

**Actual behavior**
Getting the above stacktrace


**Configuration**
```properties
quarkus.oidc.auth-server-url=http://keycloak.com
quarkus.oidc.client-id=backend
quarkus.oidc.credentials.secret=71234b
quarkus.oidc.application-type=hybrid
quarkus.oidc.roles.source=accesstoken
quarkus.oidc.roles.role-claim-path=scope
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated
```

**Environment (please complete the following information):**
 - Output of `uname -a` or `ver`: mac os & Linux
 - Output of `java -version`:  Java 11
 - GraalVM version (if different from Java): NA
 - Quarkus version or git rev: 1.10.0.CR1
 - Build tool (ie. output of `mvnw --version` or `gradlew --version`): mvnw

**Additional context**
@sberyozkin Let me know if you need any additional details


Changed files:
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java

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 b146aadb1f3..a0a8a361268 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,7 @@
 package io.quarkus.oidc.runtime;
 
+import java.util.concurrent.Executor;
+
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.event.Event;
@@ -160,4 +162,8 @@ private Uni<TenantConfigContext> getDynamicTenantContext(RoutingContext context)
     boolean isEnableHttpForwardedPrefix() {
         return enableHttpForwardedPrefix;
     }
+
+    public Executor getBlockingExecutor() {
+        return tenantConfigBean.getBlockingExecutor();
+    }
 }
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
index 7112becb340..07fa6ff9bf0 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java
@@ -15,6 +15,7 @@
 import io.quarkus.oidc.OidcTenantConfig;
 import io.quarkus.oidc.OidcTenantConfig.Roles.Source;
 import io.quarkus.oidc.OidcTokenCredential;
+import io.quarkus.runtime.BlockingOperationControl;
 import io.quarkus.security.AuthenticationFailedException;
 import io.quarkus.security.credential.TokenCredential;
 import io.quarkus.security.identity.AuthenticationRequestContext;
@@ -35,6 +36,12 @@
 
 @ApplicationScoped
 public class OidcIdentityProvider implements IdentityProvider<TokenAuthenticationRequest> {
+
+    @SuppressWarnings("deprecation")
+    private static final Uni<AccessToken> NULL_CODE_ACCESS_TOKEN_UNI = Uni.createFrom().nullItem();
+    private static final Uni<JsonObject> NULL_USER_INFO_UNI = Uni.createFrom().nullItem();
+    private static final String CODE_ACCESS_TOKEN_RESULT = "code_flow_access_token_result";
+
     @Inject
     DefaultTenantConfigResolver tenantResolver;
 
@@ -81,101 +88,153 @@ private Uni<SecurityIdentity> authenticate(TokenAuthenticationRequest request,
         if (resolvedContext.oidcConfig.publicKey.isPresent()) {
             return validateTokenWithoutOidcServer(request, resolvedContext);
         } else {
-            return validateTokenWithOidcServer(vertxContext, request, resolvedContext);
+            return validateAllTokensWithOidcServer(vertxContext, request, resolvedContext);
         }
     }
 
     @SuppressWarnings("deprecation")
-    private Uni<SecurityIdentity> validateTokenWithOidcServer(RoutingContext vertxContext, TokenAuthenticationRequest request,
+    private Uni<SecurityIdentity> validateAllTokensWithOidcServer(RoutingContext vertxContext,
+            TokenAuthenticationRequest request,
             TenantConfigContext resolvedContext) {
 
-        if (request.getToken() instanceof IdTokenCredential
-                && (resolvedContext.oidcConfig.authentication.verifyAccessToken
-                        || resolvedContext.oidcConfig.roles.source.orElse(null) == Source.accesstoken)) {
-            vertxContext.put("code_flow_access_token_result",
-                    verifyCodeFlowAccessToken(vertxContext, request, resolvedContext));
-        }
+        Uni<AccessToken> codeAccessTokenUni = verifyCodeFlowAccessTokenUni(vertxContext, request, resolvedContext);
 
-        final JsonObject userInfo = resolvedContext.oidcConfig.authentication.isUserInfoRequired()
-                ? getUserInfo(vertxContext, request, resolvedContext)
-                : null;
+        return codeAccessTokenUni.onItem().transformToUni(
+                new Function<AccessToken, Uni<? extends SecurityIdentity>>() {
+                    @Override
+                    public Uni<SecurityIdentity> apply(AccessToken codeAccessToken) {
+                        return validateTokenWithOidcServer(vertxContext, request, resolvedContext, codeAccessToken);
+                    }
+                });
+    }
 
-        return Uni.createFrom().emitter(new Consumer<UniEmitter<? super SecurityIdentity>>() {
-            @Override
-            public void accept(UniEmitter<? super SecurityIdentity> uniEmitter) {
+    @SuppressWarnings("deprecation")
+    private Uni<SecurityIdentity> validateTokenWithOidcServer(RoutingContext vertxContext, TokenAuthenticationRequest request,
+            TenantConfigContext resolvedContext, AccessToken codeAccessToken) {
 
-                resolvedContext.auth.decodeToken(request.getToken().getToken(),
-                        new Handler<AsyncResult<AccessToken>>() {
-                            @Override
-                            public void handle(AsyncResult<AccessToken> event) {
-                                if (event.failed()) {
-                                    uniEmitter.fail(new AuthenticationFailedException(event.cause()));
-                                    return;
-                                }
+        if (codeAccessToken != null) {
+            vertxContext.put(CODE_ACCESS_TOKEN_RESULT, codeAccessToken);
+        }
 
-                                // Token has been verified, as a JWT or an opaque token, possibly involving
-                                // an introspection request.
-                                final TokenCredential tokenCred = request.getToken();
+        Uni<JsonObject> userInfo = getUserInfoUni(vertxContext, request, resolvedContext);
 
-                                JsonObject tokenJson = event.result().accessToken();
+        return userInfo.onItem().transformToUni(
+                new Function<JsonObject, Uni<? extends SecurityIdentity>>() {
+                    @Override
+                    public Uni<SecurityIdentity> apply(JsonObject userInfo) {
+                        return createSecurityIdentityWithOidcServerUni(vertxContext, request, resolvedContext, userInfo);
+                    }
+                });
+    }
 
-                                if (tokenJson == null) {
-                                    // JSON token representation may be null not only if it is an opaque access token
-                                    // but also if it is JWT and no JWK with a matching kid is available, asynchronous
-                                    // JWK refresh has not finished yet, but the fallback introspection request has succeeded.
-                                    tokenJson = OidcUtils.decodeJwtContent(tokenCred.getToken());
-                                }
-                                if (tokenJson != null) {
-                                    OidcUtils.validatePrimaryJwtTokenType(resolvedContext.oidcConfig.token, tokenJson);
-                                    JsonObject rolesJson = getRolesJson(vertxContext, resolvedContext, tokenCred, tokenJson,
-                                            userInfo);
-                                    try {
-                                        SecurityIdentity securityIdentity = validateAndCreateIdentity(vertxContext, tokenCred,
-                                                resolvedContext.oidcConfig,
-                                                tokenJson, rolesJson, userInfo);
-                                        if (tokenAutoRefreshPrepared(tokenJson, vertxContext, resolvedContext.oidcConfig)) {
-                                            throw new TokenAutoRefreshException(securityIdentity);
-                                        } else {
-                                            uniEmitter.complete(securityIdentity);
-                                        }
-                                    } catch (Throwable ex) {
-                                        uniEmitter.fail(ex);
+    private Uni<SecurityIdentity> createSecurityIdentityWithOidcServerUni(RoutingContext vertxContext,
+            TokenAuthenticationRequest request, TenantConfigContext resolvedContext, final JsonObject userInfo) {
+
+        final TokenCredential tokenCred = request.getToken();
+        if (tokenCred instanceof AccessTokenCredential && ((AccessTokenCredential) tokenCred).isOpaque()) {
+            // remote introspection is required, a blocking call
+            return Uni.createFrom().emitter(
+                    new Consumer<UniEmitter<? super SecurityIdentity>>() {
+                        @Override
+                        public void accept(UniEmitter<? super SecurityIdentity> uniEmitter) {
+                            if (BlockingOperationControl.isBlockingAllowed()) {
+                                createSecurityIdentityWithOidcServer(uniEmitter, vertxContext, request, resolvedContext,
+                                        userInfo);
+                            } else {
+                                tenantResolver.getBlockingExecutor().execute(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        createSecurityIdentityWithOidcServer(uniEmitter, vertxContext, request, resolvedContext,
+                                                userInfo);
                                     }
-                                } else if (tokenCred instanceof IdTokenCredential
-                                        || tokenCred instanceof AccessTokenCredential
-                                                && !((AccessTokenCredential) tokenCred).isOpaque()) {
-                                    uniEmitter
-                                            .fail(new AuthenticationFailedException("JWT token can not be converted to JSON"));
+                                });
+                            }
+                        }
+                    });
+        } else {
+            return Uni.createFrom().emitter(new Consumer<UniEmitter<? super SecurityIdentity>>() {
+                @Override
+                public void accept(UniEmitter<? super SecurityIdentity> uniEmitter) {
+                    createSecurityIdentityWithOidcServer(uniEmitter, vertxContext, request, resolvedContext, userInfo);
+                }
+            });
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private void createSecurityIdentityWithOidcServer(UniEmitter<? super SecurityIdentity> uniEmitter,
+            RoutingContext vertxContext,
+            TokenAuthenticationRequest request, TenantConfigContext resolvedContext, final JsonObject userInfo) {
+        resolvedContext.auth.decodeToken(request.getToken().getToken(),
+                new Handler<AsyncResult<AccessToken>>() {
+                    @Override
+                    public void handle(AsyncResult<AccessToken> event) {
+                        if (event.failed()) {
+                            uniEmitter.fail(new AuthenticationFailedException(event.cause()));
+                            return;
+                        }
+
+                        // Token has been verified, as a JWT or an opaque token, possibly involving
+                        // an introspection request.
+                        final TokenCredential tokenCred = request.getToken();
+
+                        JsonObject tokenJson = event.result().accessToken();
+
+                        if (tokenJson == null) {
+                            // JSON token representation may be null not only if it is an opaque access token
+                            // but also if it is JWT and no JWK with a matching kid is available, asynchronous
+                            // JWK refresh has not finished yet, but the fallback introspection request has succeeded.
+                            tokenJson = OidcUtils.decodeJwtContent(tokenCred.getToken());
+                        }
+                        if (tokenJson != null) {
+                            OidcUtils.validatePrimaryJwtTokenType(resolvedContext.oidcConfig.token, tokenJson);
+                            JsonObject rolesJson = getRolesJson(vertxContext, resolvedContext, tokenCred, tokenJson,
+                                    userInfo);
+                            try {
+                                SecurityIdentity securityIdentity = validateAndCreateIdentity(vertxContext, tokenCred,
+                                        resolvedContext.oidcConfig,
+                                        tokenJson, rolesJson, userInfo);
+                                if (tokenAutoRefreshPrepared(tokenJson, vertxContext, resolvedContext.oidcConfig)) {
+                                    uniEmitter.fail(new TokenAutoRefreshException(securityIdentity));
                                 } else {
-                                    // Opaque Bearer Access Token
-                                    QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
-                                    builder.addCredential(tokenCred);
-                                    OidcUtils.setSecurityIdentityUserInfo(builder, userInfo);
-                                    if (event.result().principal().containsKey("username")) {
-                                        final String userName = event.result().principal().getString("username");
-                                        builder.setPrincipal(new Principal() {
-                                            @Override
-                                            public String getName() {
-                                                return userName;
-                                            }
-                                        });
-                                    }
-                                    if (event.result().principal().containsKey("scope")) {
-                                        for (String role : event.result().principal().getString("scope").split(" ")) {
-                                            builder.addRole(role.trim());
-                                        }
-                                    }
-                                    if (userInfo != null) {
-                                        OidcUtils.setSecurityIdentityRoles(builder, resolvedContext.oidcConfig, userInfo);
+                                    uniEmitter.complete(securityIdentity);
+                                }
+                            } catch (Throwable ex) {
+                                uniEmitter.fail(ex);
+                            }
+                        } else if (tokenCred instanceof IdTokenCredential
+                                || tokenCred instanceof AccessTokenCredential
+                                        && !((AccessTokenCredential) tokenCred).isOpaque()) {
+                            uniEmitter
+                                    .fail(new AuthenticationFailedException("JWT token can not be converted to JSON"));
+                        } else {
+                            // Opaque Bearer Access Token
+                            QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
+                            builder.addCredential(tokenCred);
+                            OidcUtils.setSecurityIdentityUserInfo(builder, userInfo);
+                            if (event.result().principal().containsKey("username")) {
+                                final String userName = event.result().principal().getString("username");
+                                builder.setPrincipal(new Principal() {
+                                    @Override
+                                    public String getName() {
+                                        return userName;
                                     }
-                                    OidcUtils.setBlockinApiAttribute(builder, vertxContext);
-                                    OidcUtils.setTenantIdAttribute(builder, resolvedContext.oidcConfig);
-                                    uniEmitter.complete(builder.build());
+                                });
+                            }
+                            if (event.result().principal().containsKey("scope")) {
+                                for (String role : event.result().principal().getString("scope").split(" ")) {
+                                    builder.addRole(role.trim());
                                 }
                             }
-                        });
-            }
-        });
+                            if (userInfo != null) {
+                                OidcUtils.setSecurityIdentityRoles(builder, resolvedContext.oidcConfig, userInfo);
+                            }
+                            OidcUtils.setBlockinApiAttribute(builder, vertxContext);
+                            OidcUtils.setTenantIdAttribute(builder, resolvedContext.oidcConfig);
+                            uniEmitter.complete(builder.build());
+                        }
+                    }
+                });
     }
 
     private static boolean tokenAutoRefreshPrepared(JsonObject tokenJson, RoutingContext vertxContext,
@@ -206,8 +265,8 @@ private static JsonObject getRolesJson(RoutingContext vertxContext, TenantConfig
                 rolesJson = userInfo;
             } else if (tokenCred instanceof IdTokenCredential
                     && resolvedContext.oidcConfig.roles.source.get() == Source.accesstoken) {
-                AccessToken result = (AccessToken) vertxContext.get("code_flow_access_token_result");
-                rolesJson = result.accessToken();
+                AccessToken result = (AccessToken) vertxContext.get(CODE_ACCESS_TOKEN_RESULT);
+                rolesJson = result != null ? result.accessToken() : null;
                 if (rolesJson == null) {
                     // JSON token representation may be null not only if it is an opaque access token
                     // but also if it is JWT and no JWK with a matching kid is available, asynchronous
@@ -224,23 +283,56 @@ private static JsonObject getRolesJson(RoutingContext vertxContext, TenantConfig
     }
 
     @SuppressWarnings("deprecation")
-    private static AccessToken verifyCodeFlowAccessToken(RoutingContext vertxContext, TokenAuthenticationRequest request,
+    private Uni<AccessToken> verifyCodeFlowAccessTokenUni(RoutingContext vertxContext, TokenAuthenticationRequest request,
             TenantConfigContext resolvedContext) {
-        return Uni.createFrom().emitter(new Consumer<UniEmitter<? super AccessToken>>() {
-            @Override
-            public void accept(UniEmitter<? super AccessToken> uniEmitter) {
-                resolvedContext.auth.decodeToken((String) vertxContext.get("access_token"),
-                        new Handler<AsyncResult<AccessToken>>() {
+        if (request.getToken() instanceof IdTokenCredential
+                && (resolvedContext.oidcConfig.authentication.verifyAccessToken
+                        || resolvedContext.oidcConfig.roles.source.orElse(null) == Source.accesstoken)) {
+            final String codeAccessToken = (String) vertxContext.get("access_token");
+            if (OidcUtils.isOpaqueToken(codeAccessToken)) {
+                // remote introspection is required, a blocking call
+                return Uni.createFrom().emitter(
+                        new Consumer<UniEmitter<? super AccessToken>>() {
                             @Override
-                            public void handle(AsyncResult<AccessToken> event) {
-                                if (event.failed()) {
-                                    uniEmitter.fail(new AuthenticationFailedException(event.cause()));
+                            public void accept(UniEmitter<? super AccessToken> uniEmitter) {
+                                if (BlockingOperationControl.isBlockingAllowed()) {
+                                    verifyCodeFlowAccessToken(uniEmitter, resolvedContext, codeAccessToken);
+                                } else {
+                                    tenantResolver.getBlockingExecutor().execute(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            verifyCodeFlowAccessToken(uniEmitter, resolvedContext, codeAccessToken);
+                                        }
+                                    });
                                 }
-                                uniEmitter.complete(event.result());
                             }
                         });
+            } else {
+                return Uni.createFrom().emitter(new Consumer<UniEmitter<? super AccessToken>>() {
+                    @Override
+                    public void accept(UniEmitter<? super AccessToken> uniEmitter) {
+                        verifyCodeFlowAccessToken(uniEmitter, resolvedContext, codeAccessToken);
+                    }
+                });
             }
-        }).await().indefinitely();
+        } else {
+            return NULL_CODE_ACCESS_TOKEN_UNI;
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private void verifyCodeFlowAccessToken(UniEmitter<? super AccessToken> uniEmitter,
+            TenantConfigContext resolvedContext, String codeAccessToken) {
+        resolvedContext.auth.decodeToken(codeAccessToken,
+                new Handler<AsyncResult<AccessToken>>() {
+                    @Override
+                    public void handle(AsyncResult<AccessToken> event) {
+                        if (event.failed()) {
+                            uniEmitter.fail(new AuthenticationFailedException(event.cause()));
+                        }
+                        uniEmitter.complete(event.result());
+                    }
+                });
     }
 
     private static Uni<SecurityIdentity> validateTokenWithoutOidcServer(TokenAuthenticationRequest request,
@@ -266,33 +358,51 @@ private static Uni<SecurityIdentity> validateTokenWithoutOidcServer(TokenAuthent
         }
     }
 
-    private static JsonObject getUserInfo(RoutingContext vertxContext, TokenAuthenticationRequest request,
+    private Uni<JsonObject> getUserInfoUni(RoutingContext vertxContext, TokenAuthenticationRequest request,
             TenantConfigContext resolvedContext) {
+        if (resolvedContext.oidcConfig.authentication.isUserInfoRequired()) {
+            return Uni.createFrom().emitter(
+                    new Consumer<UniEmitter<? super JsonObject>>() {
+                        @Override
+                        public void accept(UniEmitter<? super JsonObject> uniEmitter) {
+                            if (BlockingOperationControl.isBlockingAllowed()) {
+                                createUserInfoToken(uniEmitter, vertxContext, request, resolvedContext);
+                            } else {
+                                tenantResolver.getBlockingExecutor().execute(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        createUserInfoToken(uniEmitter, vertxContext, request, resolvedContext);
+                                    }
+                                });
+                            }
+                        }
+                    });
+        } else {
+            return NULL_USER_INFO_UNI;
+        }
+    }
+
+    private void createUserInfoToken(UniEmitter<? super JsonObject> uniEmitter, RoutingContext vertxContext,
+            TokenAuthenticationRequest request, TenantConfigContext resolvedContext) {
         OAuth2TokenImpl tokenImpl = new OAuth2TokenImpl(resolvedContext.auth, new JsonObject());
         String accessToken = vertxContext.get("access_token");
         if (accessToken == null) {
             accessToken = request.getToken().getToken();
         }
         tokenImpl.principal().put("access_token", accessToken);
-        return Uni.createFrom().emitter(
-                new Consumer<UniEmitter<? super JsonObject>>() {
-                    @Override
-                    public void accept(UniEmitter<? super JsonObject> uniEmitter) {
-                        tokenImpl.userInfo(new Handler<AsyncResult<JsonObject>>() {
-                            @Override
-                            public void handle(AsyncResult<JsonObject> event) {
-                                if (event.failed()) {
-                                    uniEmitter.fail(new AuthenticationFailedException(event.cause()));
-                                } else {
-                                    uniEmitter.complete(event.result());
-                                }
-                            }
-                        });
-                    }
-                }).await().indefinitely();
+        tokenImpl.userInfo(new Handler<AsyncResult<JsonObject>>() {
+            @Override
+            public void handle(AsyncResult<JsonObject> event) {
+                if (event.failed()) {
+                    uniEmitter.fail(new AuthenticationFailedException(event.cause()));
+                } else {
+                    uniEmitter.complete(event.result());
+                }
+            }
+        });
     }
 
     private static boolean isTenantBlocking(TenantConfigContext resolvedContext) {
-        return resolvedContext.oidcConfig.token.refreshExpired || resolvedContext.oidcConfig.authentication.userInfoRequired;
+        return resolvedContext.oidcConfig.token.refreshExpired;
     }
 }
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
index c9939db7ae1..23c4c2c6a56 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java
@@ -68,42 +68,42 @@ public TenantConfigBean get() {
                         new Function<OidcTenantConfig, Uni<TenantConfigContext>>() {
                             @Override
                             public Uni<TenantConfigContext> apply(OidcTenantConfig config) {
-                                if (BlockingOperationControl.isBlockingAllowed()) {
-                                    try {
-                                        return Uni.createFrom().item(createDynamicTenantContext(vertxValue, config,
-                                                config.getTenantId().get()));
-                                    } catch (Throwable t) {
-                                        return Uni.createFrom().failure(t);
-                                    }
-                                } else {
-                                    return Uni.createFrom().emitter(new Consumer<UniEmitter<? super TenantConfigContext>>() {
-                                        @Override
-                                        public void accept(UniEmitter<? super TenantConfigContext> uniEmitter) {
+
+                                return Uni.createFrom().emitter(new Consumer<UniEmitter<? super TenantConfigContext>>() {
+                                    @Override
+                                    public void accept(UniEmitter<? super TenantConfigContext> uniEmitter) {
+                                        if (BlockingOperationControl.isBlockingAllowed()) {
+                                            createDynamicTenantContext(uniEmitter, vertxValue, config,
+                                                    config.getTenantId().get());
+                                        } else {
                                             ExecutorRecorder.getCurrent().execute(new Runnable() {
                                                 @Override
                                                 public void run() {
-                                                    try {
-                                                        uniEmitter.complete(createDynamicTenantContext(vertxValue, config,
-                                                                config.getTenantId().get()));
-                                                    } catch (Throwable t) {
-                                                        uniEmitter.fail(t);
-                                                    }
+                                                    createDynamicTenantContext(uniEmitter, vertxValue, config,
+                                                            config.getTenantId().get());
                                                 }
                                             });
                                         }
-                                    });
-                                }
+                                    }
+                                });
+
                             }
-                        });
+                        },
+                        ExecutorRecorder.getCurrent());
             }
         };
     }
 
-    private TenantConfigContext createDynamicTenantContext(Vertx vertx, OidcTenantConfig oidcConfig, String tenantId) {
-        if (!dynamicTenantsConfig.containsKey(tenantId)) {
-            dynamicTenantsConfig.putIfAbsent(tenantId, createTenantContext(vertx, oidcConfig, tenantId));
+    private void createDynamicTenantContext(UniEmitter<? super TenantConfigContext> uniEmitter, Vertx vertx,
+            OidcTenantConfig oidcConfig, String tenantId) {
+        try {
+            if (!dynamicTenantsConfig.containsKey(tenantId)) {
+                dynamicTenantsConfig.putIfAbsent(tenantId, createTenantContext(vertx, oidcConfig, tenantId));
+            }
+            uniEmitter.complete(dynamicTenantsConfig.get(tenantId));
+        } catch (Throwable t) {
+            uniEmitter.fail(t);
         }
-        return dynamicTenantsConfig.get(tenantId);
     }
 
     private TenantConfigContext createTenantContext(Vertx vertx, OidcTenantConfig oidcConfig, String tenantId) {
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java
index 47f83de514e..b3a5973c0cb 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/TenantConfigBean.java
@@ -1,6 +1,7 @@
 package io.quarkus.oidc.runtime;
 
 import java.util.Map;
+import java.util.concurrent.Executor;
 import java.util.function.Function;
 
 import io.quarkus.oidc.OidcTenantConfig;
@@ -12,16 +13,19 @@ public class TenantConfigBean {
     private final Map<String, TenantConfigContext> dynamicTenantsConfig;
     private final TenantConfigContext defaultTenant;
     private final Function<OidcTenantConfig, Uni<TenantConfigContext>> tenantConfigContextFactory;
+    private final Executor blockingExecutor;
 
     public TenantConfigBean(
             Map<String, TenantConfigContext> staticTenantsConfig,
             Map<String, TenantConfigContext> dynamicTenantsConfig,
             TenantConfigContext defaultTenant,
-            Function<OidcTenantConfig, Uni<TenantConfigContext>> tenantConfigContextFactory) {
+            Function<OidcTenantConfig, Uni<TenantConfigContext>> tenantConfigContextFactory,
+            Executor blockingExecutor) {
         this.staticTenantsConfig = staticTenantsConfig;
         this.dynamicTenantsConfig = dynamicTenantsConfig;
         this.defaultTenant = defaultTenant;
         this.tenantConfigContextFactory = tenantConfigContextFactory;
+        this.blockingExecutor = blockingExecutor;
     }
 
     public Map<String, TenantConfigContext> getStaticTenantsConfig() {
@@ -39,4 +43,8 @@ public Function<OidcTenantConfig, Uni<TenantConfigContext>> getTenantConfigConte
     public Map<String, TenantConfigContext> getDynamicTenantsConfig() {
         return dynamicTenantsConfig;
     }
+
+    public Executor getBlockingExecutor() {
+        return blockingExecutor;
+    }
 }