diff --git a/integration-tests/oidc-wiremock/pom.xml b/integration-tests/oidc-wiremock/pom.xml
index 155a9ab6893896..196cdce43b11d5 100644
--- a/integration-tests/oidc-wiremock/pom.xml
+++ b/integration-tests/oidc-wiremock/pom.xml
@@ -49,6 +49,17 @@
             <artifactId>jakarta.servlet-api</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>net.sourceforge.htmlunit</groupId>
+            <artifactId>htmlunit</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
         <!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
         <dependency>
             <groupId>io.quarkus</groupId>
diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowResource.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowResource.java
new file mode 100644
index 00000000000000..ad9fe768cf9043
--- /dev/null
+++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CodeFlowResource.java
@@ -0,0 +1,15 @@
+package io.quarkus.it.keycloak;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+import io.quarkus.security.Authenticated;
+
+@Path("/code-flow")
+@Authenticated
+public class CodeFlowResource {
+
+    @GET
+    public void access() {
+    }
+}
diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java
new file mode 100644
index 00000000000000..4b8c71899a9842
--- /dev/null
+++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantResolver.java
@@ -0,0 +1,19 @@
+package io.quarkus.it.keycloak;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import io.quarkus.oidc.TenantResolver;
+import io.vertx.ext.web.RoutingContext;
+
+@ApplicationScoped
+public class CustomTenantResolver implements TenantResolver {
+
+    @Override
+    public String resolve(RoutingContext context) {
+        String path = context.normalisedPath();
+        if (path.endsWith("code-flow")) {
+            return "code-flow";
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/integration-tests/oidc-wiremock/src/main/resources/META-INF/resources/index.html b/integration-tests/oidc-wiremock/src/main/resources/META-INF/resources/index.html
new file mode 100644
index 00000000000000..a5410d867f2f8a
--- /dev/null
+++ b/integration-tests/oidc-wiremock/src/main/resources/META-INF/resources/index.html
@@ -0,0 +1,8 @@
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Welcome to Test App</title>
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties
index 3f6978e14c1530..6e35a7f68e32f6 100644
--- a/integration-tests/oidc-wiremock/src/main/resources/application.properties
+++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties
@@ -2,6 +2,12 @@
 quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
 quarkus.oidc.client-id=quarkus-app
 quarkus.oidc.credentials.secret=secret
-quarkus.oidc.token.principal-claim=email
+quarkus.oidc.authentication.scopes=profile,email,phone
 
-smallrye.jwt.sign.key-location=privateKey.jwk
+quarkus.oidc.code-flow.auth-server-url=${keycloak.url}/realms/quarkus/
+quarkus.oidc.code-flow.client-id=quarkus-web-app
+quarkus.oidc.code-flow.credentials.secret=secret
+quarkus.oidc.code-flow.application-type=web-app
+quarkus.oidc.code-flow.authentication.redirect-path=/index.html
+
+smallrye.jwt.sign.key-location=privateKey.jwk
\ No newline at end of file
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
new file mode 100644
index 00000000000000..e3b37e303cd7a2
--- /dev/null
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
@@ -0,0 +1,68 @@
+package io.quarkus.it.keycloak;
+
+import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.WebRequest;
+import com.gargoylesoftware.htmlunit.WebResponse;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.util.Cookie;
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URI;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@QuarkusTest
+@QuarkusTestResource(KeycloakTestResource.class)
+public class CodeFlowAuthorizationTest {
+
+    @Test
+    public void testCodeFlow() throws IOException {
+        try (final WebClient webClient = createWebClient()) {
+            webClient.getOptions().setRedirectEnabled(false);
+            WebResponse webResponse = webClient
+                    .loadWebResponse(new WebRequest(URI.create("http://localhost:8081/code-flow").toURL()));
+            verifyLocationHeader(webClient, webResponse.getResponseHeaderValue("location"), "code-flow", "index.html", false);
+
+            webClient.getOptions().setRedirectEnabled(true);
+            HtmlPage page = webClient.getPage("http://localhost:8081/code-flow");
+
+            HtmlForm form = page.getFormByName("form");
+            form.getInputByName("username").type("alice");
+            form.getInputByName("password").type("alice");
+
+            page = form.getInputByValue("login").click();
+
+            assertEquals("Welcome to Test App", page.getTitleText());
+        }
+    }
+
+    private WebClient createWebClient() {
+        WebClient webClient = new WebClient();
+        webClient.setCssErrorHandler(new SilentCssErrorHandler());
+        return webClient;
+    }
+
+    private void verifyLocationHeader(WebClient webClient, String loc, String tenant, String path, boolean forceHttps) {
+        assertTrue(loc.contains("/auth"));
+        String scheme = forceHttps ? "https" : "http";
+        assertTrue(loc.contains("redirect_uri=" + scheme + "%3A%2F%2Flocalhost%3A8081%2F" + path));
+        assertTrue(loc.contains("state=" + getStateCookieStateParam(webClient, tenant)));
+        assertTrue(loc.contains("scope=openid"));
+        assertTrue(loc.contains("response_type=code"));
+        assertTrue(loc.contains("client_id=quarkus-web-app"));
+    }
+
+    private Cookie getStateCookie(WebClient webClient, String tenantId) {
+        return webClient.getCookieManager().getCookie("q_auth" + (tenantId == null ? "" : "_" + tenantId));
+    }
+
+    private String getStateCookieStateParam(WebClient webClient, String tenantId) {
+        return getStateCookie(webClient, tenantId).getValue().split("\\|")[0];
+    }
+}
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakTestResource.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakTestResource.java
index 27357e02c04f2c..725cea37e65ecc 100644
--- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakTestResource.java
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/KeycloakTestResource.java
@@ -1,27 +1,26 @@
 package io.quarkus.it.keycloak;
 
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
+import com.google.common.collect.ImmutableSet;
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.jboss.logging.Logger;
+
+import javax.ws.rs.core.MediaType;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
 import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
 import static com.github.tomakehurst.wiremock.client.WireMock.get;
 import static com.github.tomakehurst.wiremock.client.WireMock.matching;
 import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
 import static java.util.Collections.emptySet;
 import static java.util.stream.Collectors.joining;
 
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-import javax.ws.rs.core.MediaType;
-
-import org.jboss.logging.Logger;
-
-import com.github.tomakehurst.wiremock.WireMockServer;
-import com.github.tomakehurst.wiremock.client.WireMock;
-import com.google.common.collect.ImmutableSet;
-
-import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
-
 public class KeycloakTestResource implements QuarkusTestResourceLifecycleManager {
 
     private static final Logger LOG = Logger.getLogger(KeycloakTestResource.class);
@@ -31,7 +30,10 @@ public class KeycloakTestResource implements QuarkusTestResourceLifecycleManager
     @Override
     public Map<String, String> start() {
 
-        server = new WireMockServer(wireMockConfig().dynamicPort());
+        server = new WireMockServer(
+                wireMockConfig()
+                        .extensions(new ResponseTemplateTransformer(false))
+                        .dynamicPort());
         server.start();
 
         server.stubFor(
@@ -42,7 +44,8 @@ public Map<String, String> start() {
                                         "    \"jwks_uri\": \"" + server.baseUrl()
                                         + "/auth/realms/quarkus/protocol/openid-connect/certs\",\n" +
                                         "    \"token_introspection_endpoint\": \"" + server.baseUrl()
-                                        + "/auth/realms/quarkus/protocol/openid-connect/token/introspect\"\n" +
+                                        + "/auth/realms/quarkus/protocol/openid-connect/token/introspect\",\n" +
+                                        "    \"authorization_endpoint\": \"" + server.baseUrl() + "/auth/realms/quarkus\"" +
                                         "}")));
 
         server.stubFor(
@@ -71,8 +74,42 @@ public Map<String, String> start() {
         // Invalid
         defineInvalidIntrospectionMockTokenStubForUserWithRoles("expired", emptySet());
 
+        // Login Page
+        server.stubFor(
+                get(urlPathMatching("/auth/realms/quarkus"))
+                        .willReturn(aResponse()
+                                .withHeader("Content-Type", MediaType.TEXT_HTML)
+                                .withBody("<html>\n" +
+                                        "<body>\n" +
+                                        " <form action=\"/login\" name=\"form\">\n" +
+                                        "  <input type=\"text\" id=\"username\" name=\"username\"/>\n" +
+                                        "  <input type=\"password\" id=\"password\" name=\"password\"/>\n" +
+                                        "  <input type=\"hidden\" id=\"state\" name=\"state\" value=\"{{request.query.state}}\"/>\n"
+                                        +
+                                        "  <input type=\"hidden\" id=\"redirect_uri\" name=\"redirect_uri\" value=\"{{request.query.redirect_uri}}\"/>\n"
+                                        +
+                                        "  <input type=\"submit\" id=\"login\" value=\"login\"/>\n" +
+                                        "</form>\n" +
+                                        "</body>\n" +
+                                        "</html> ")
+                                .withTransformers("response-template")));
+
+        // Login Request
+        server.stubFor(
+                get(urlPathMatching("/login"))
+                        .willReturn(aResponse()
+                                .withHeader("Location", "{{request.query.redirect_uri}}")
+                                .withHeader("state", "{{request.query.state}}")
+                                .withStatus(302)
+                                .withTransformers("response-template")));
+
         LOG.infof("Keycloak started in mock mode: %s", server.baseUrl());
-        return Collections.singletonMap("quarkus.oidc.auth-server-url", server.baseUrl() + "/auth/realms/quarkus");
+        Map<String, String> conf = new HashMap<>();
+        conf.put("quarkus.oidc.auth-server-url", server.baseUrl() + "/auth/realms/quarkus");
+        conf.put("quarkus.oidc.code-flow.auth-server-url", server.baseUrl() + "/auth/realms/quarkus");
+        conf.put("keycloak-url", server.baseUrl());
+
+        return conf;
     }
 
     private void defineValidIntrospectionMockTokenStubForUserWithRoles(String user, Set<String> roles) {