Repository: quarkusio/quarkus
Issue: OIDC Extension does not work anymore with Azure AD and Quarkus 1.13.0 

## Describe the bug
Looks like there is a regression when updating from Quarkus `1.12.x` to `1.13.0`

We get a `401 Unauthorized` when we try to authenticate through Azure AD with Quarkus OIDC, this used to work fine in the previous versions. I did some debugging and turns out Azure AD doesn't like the chunked transfer headers.

I intercepted the requests to Azure AD with ProxyMan and Quarkus running locally. Below are the cURL exports from those requests.

Quarkus `1.12.x`
```
curl 'https://login.microsoftonline.com/<<tenant>>/oauth2/v2.0/token' \\
-X POST \\
-H 'Content-Length: 878' \\       
-H 'Accept: application/json,application/x-www-form-urlencoded;q=0.9' \\
-H 'Authorization: Basic <<secret>>' \\
-H 'Content-Type: application/x-www-form-urlencoded' \\
-H 'Host: login.microsoftonline.com' \\
--proxy http://localhost:9090 \\
-d 'code=<<code>>&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fauth&grant_type=authorization_code'
```

Quarkus `1.13.0`
```
curl 'https://login.microsoftonline.com/<<tenant>>/oauth2/v2.0/token' \\
-X POST \\
-H 'Transfer-Encoding: chunked' \\
-H 'Content-Type: application/x-www-form-urlencoded' \\
-H 'User-Agent: Vert.x-WebClient/3.9.5' \\
-H 'Authorization: Basic <<secret>>' \\
-H 'Host: login.microsoftonline.com' \\
--proxy http://localhost:9090 \\
-d 'grant_type=authorization_code&code=<<code>>&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fauth'
```

The last request returns a `404 Not Found` from Azure AD. It looks like that Azure AD doesn't like the chunked transfer encoding from the Vert.x WebClient. If I remove the header `'Transfer-Encoding: chunked'` I can successfully obtain a token with the above cURL request. Can we set some config for the Vert.x WebClient or do we need to dive a bit deeper here? I would be glad to help out and test some more.

### Expected behavior
Successful authentication through Azure AD with Quarkus OIDC.

### Actual behavior
Quarkus returns a 401 Unauthorized after successfully authenticating to Azure AD as a user.

## To Reproduce

Link to a small reproducer (preferably a Maven project if the issue is not Gradle-specific).

Or attach an archive containing the reproducer to the issue.

Steps to reproduce the behavior:
1. Set up a project with Quarkus OIDC
2. Set the needed properties for your OIDC server with Azure AD
3. Try to login with Azure AD
4. You get a 401 Unauthorized from Quarkus

### Configuration
```properties
# Add your application.properties here, if applicable.

```

### Screenshots
(If applicable, add screenshots to help explain your problem.)

## Environment (please complete the following information):

### Output of `uname -a` or `ver`
```
Darwin Willems-MBP.i.btp34.nl 20.3.0 Darwin Kernel Version 20.3.0: Thu Jan 21 00:07:06 PST 2021; root:xnu-7195.81.3~1/RELEASE_X86_64 x86_64
```

### Output of `java -version`
```
openjdk version "11.0.10" 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.10+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.10+9, mixed mode)
```

### GraalVM version (if different from Java)

### Quarkus version or git rev
`1.13.0.FINAL`
### Build tool (ie. output of `mvnw --version` or `gradlew --version`)
```
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /Users/wjglerum/.m2/wrapper/dists/apache-maven-3.6.3-bin/1iopthnavndlasol9gbrbg6bf2/apache-maven-3.6.3
Java version: 11.0.10, vendor: AdoptOpenJDK, runtime: /Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home
Default locale: en_NL, platform encoding: UTF-8
OS name: "mac os x", version: "10.16", arch: "x86_64", family: "mac"
```
## Additional context
(Add any other context about the problem here.)


Changed files:
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java
extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java
extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java

diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java
index e8b87012149..5f883af8300 100644
--- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java
+++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/OidcClientImpl.java
@@ -70,15 +70,17 @@ public Uni<Tokens> refreshTokens(String refreshToken) {
         return getJsonResponse(refreshGrantParams, true);
     }
 
-    private Uni<Tokens> getJsonResponse(MultiMap reqBody, boolean refresh) {
+    private Uni<Tokens> getJsonResponse(MultiMap formBody, boolean refresh) {
         //Uni needs to be lazy by default, we don't send the request unless
         //something has subscribed to it. This is important for the CAS state
         //management in TokensHelper
         return Uni.createFrom().deferred(new Supplier<Uni<? extends Tokens>>() {
             @Override
             public Uni<Tokens> get() {
-                MultiMap body = reqBody;
+                MultiMap body = formBody;
                 HttpRequest<Buffer> request = client.postAbs(tokenRequestUri);
+                request.putHeader(HttpHeaders.CONTENT_TYPE.toString(),
+                        HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString());
                 if (clientSecretBasicAuthScheme != null) {
                     request.putHeader(AUTHORIZATION_HEADER, clientSecretBasicAuthScheme);
                 } else if (clientJwtKey != null) {
@@ -88,7 +90,7 @@ public Uni<Tokens> get() {
                     body.add(OidcConstants.CLIENT_ASSERTION, OidcCommonUtils.signJwtWithKey(oidcConfig, clientJwtKey));
                 }
                 // Retry up to three times with a one second delay between the retries if the connection is closed
-                Uni<HttpResponse<Buffer>> response = request.sendForm(body)
+                Uni<HttpResponse<Buffer>> response = request.sendBuffer(OidcCommonUtils.encodeForm(body))
                         .onFailure(ConnectException.class)
                         .retry()
                         .atMost(3);
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
index b037aeb527f..e9cb3de9363 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
@@ -1,11 +1,13 @@
 package io.quarkus.oidc.common.runtime;
 
 import java.io.InputStream;
+import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.security.Key;
 import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.util.Base64;
+import java.util.Map;
 import java.util.Optional;
 
 import javax.crypto.SecretKey;
@@ -22,8 +24,13 @@
 import io.vertx.core.http.HttpClientOptions;
 import io.vertx.core.json.JsonObject;
 import io.vertx.core.net.ProxyOptions;
+import io.vertx.mutiny.core.MultiMap;
+import io.vertx.mutiny.core.buffer.Buffer;
 
 public class OidcCommonUtils {
+    static final byte AMP = '&';
+    static final byte EQ = '=';
+
     private OidcCommonUtils() {
 
     }
@@ -55,6 +62,27 @@ public static String prependSlash(String path) {
         return !path.startsWith("/") ? "/" + path : path;
     }
 
+    public static Buffer encodeForm(MultiMap form) {
+        Buffer buffer = Buffer.buffer();
+        for (Map.Entry<String, String> entry : form) {
+            if (buffer.length() != 0) {
+                buffer.appendByte(AMP);
+            }
+            buffer.appendString(entry.getKey());
+            buffer.appendByte(EQ);
+            buffer.appendString(urlEncode(entry.getValue()));
+        }
+        return buffer;
+    }
+
+    public static String urlEncode(String value) {
+        try {
+            return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig tlsConfig, HttpClientOptions options) {
         boolean trustAll = oidcConfig.tls.verification.isPresent() ? oidcConfig.tls.verification.get() == Verification.NONE
                 : tlsConfig.trustAll;
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 fa5aeba7b3e..dc927bf2177 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
@@ -4,8 +4,6 @@
 import static io.quarkus.oidc.runtime.OidcIdentityProvider.REFRESH_TOKEN_GRANT_RESPONSE;
 
 import java.net.URI;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
 import java.security.Permission;
 import java.util.ArrayList;
 import java.util.List;
@@ -28,6 +26,7 @@
 import io.quarkus.oidc.OidcTenantConfig.Authentication;
 import io.quarkus.oidc.RefreshToken;
 import io.quarkus.oidc.SecurityEvent;
+import io.quarkus.oidc.common.runtime.OidcCommonUtils;
 import io.quarkus.oidc.common.runtime.OidcConstants;
 import io.quarkus.runtime.BlockingOperationControl;
 import io.quarkus.security.AuthenticationCompletionException;
@@ -204,20 +203,22 @@ public Uni<ChallengeData> getChallengeInternal(RoutingContext context, TenantCon
 
         // client_id
         codeFlowParams.append(AMP).append(OidcConstants.CLIENT_ID).append(EQ)
-                .append(urlEncode(configContext.oidcConfig.clientId.get()));
+                .append(OidcCommonUtils.urlEncode(configContext.oidcConfig.clientId.get()));
 
         // scope
         List<String> scopes = new ArrayList<>();
         scopes.add("openid");
         configContext.oidcConfig.getAuthentication().scopes.ifPresent(scopes::addAll);
-        codeFlowParams.append(AMP).append(OidcConstants.TOKEN_SCOPE).append(EQ).append(urlEncode(String.join(" ", scopes)));
+        codeFlowParams.append(AMP).append(OidcConstants.TOKEN_SCOPE).append(EQ)
+                .append(OidcCommonUtils.urlEncode(String.join(" ", scopes)));
 
         // redirect_uri
         String redirectPath = getRedirectPath(configContext, context);
         String redirectUriParam = buildUri(context, isForceHttps(configContext), redirectPath);
         LOG.debugf("Authentication request redirect_uri parameter: %s", redirectUriParam);
 
-        codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_REDIRECT_URI).append(EQ).append(urlEncode(redirectUriParam));
+        codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_REDIRECT_URI).append(EQ)
+                .append(OidcCommonUtils.urlEncode(redirectUriParam));
 
         // state
         codeFlowParams.append(AMP).append(OidcConstants.CODE_FLOW_STATE).append(EQ)
@@ -226,7 +227,8 @@ public Uni<ChallengeData> getChallengeInternal(RoutingContext context, TenantCon
         // extra redirect parameters, see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequests
         if (configContext.oidcConfig.authentication.getExtraParams() != null) {
             for (Map.Entry<String, String> entry : configContext.oidcConfig.authentication.getExtraParams().entrySet()) {
-                codeFlowParams.append(AMP).append(entry.getKey()).append(EQ).append(urlEncode(entry.getValue()));
+                codeFlowParams.append(AMP).append(entry.getKey()).append(EQ)
+                        .append(OidcCommonUtils.urlEncode(entry.getValue()));
             }
         }
 
@@ -236,14 +238,6 @@ public Uni<ChallengeData> getChallengeInternal(RoutingContext context, TenantCon
                 authorizationURL));
     }
 
-    private static String urlEncode(String value) {
-        try {
-            return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
-        } catch (Exception ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
     private Uni<SecurityIdentity> performCodeFlow(IdentityProviderManager identityProviderManager,
             RoutingContext context, TenantConfigContext configContext, String code) {
 
diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java
index 8e3eec0b7e5..4e12948ccd0 100644
--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java
+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClient.java
@@ -95,21 +95,22 @@ public Uni<AuthorizationCodeTokens> refreshAuthorizationCodeTokens(String refres
         return getHttpResponse(metadata.getTokenUri(), refreshGrantParams).transform(resp -> getAuthorizationCodeTokens(resp));
     }
 
-    private UniOnItem<HttpResponse<Buffer>> getHttpResponse(String uri, MultiMap reqBody) {
+    private UniOnItem<HttpResponse<Buffer>> getHttpResponse(String uri, MultiMap formBody) {
         HttpRequest<Buffer> request = client.postAbs(uri);
+        request.putHeader(HttpHeaders.CONTENT_TYPE.toString(), HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString());
         if (clientSecretBasicAuthScheme != null) {
             request.putHeader(AUTHORIZATION_HEADER, clientSecretBasicAuthScheme);
         } else if (clientJwtKey != null) {
-            reqBody.add(OidcConstants.CLIENT_ASSERTION_TYPE, OidcConstants.JWT_BEARER_CLIENT_ASSERTION_TYPE);
-            reqBody.add(OidcConstants.CLIENT_ASSERTION, OidcCommonUtils.signJwtWithKey(oidcConfig, clientJwtKey));
+            formBody.add(OidcConstants.CLIENT_ASSERTION_TYPE, OidcConstants.JWT_BEARER_CLIENT_ASSERTION_TYPE);
+            formBody.add(OidcConstants.CLIENT_ASSERTION, OidcCommonUtils.signJwtWithKey(oidcConfig, clientJwtKey));
         } else if (OidcCommonUtils.isClientSecretPostAuthRequired(oidcConfig.credentials)) {
-            reqBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
-            reqBody.add(OidcConstants.CLIENT_SECRET, OidcCommonUtils.clientSecret(oidcConfig.credentials));
+            formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
+            formBody.add(OidcConstants.CLIENT_SECRET, OidcCommonUtils.clientSecret(oidcConfig.credentials));
         } else {
-            reqBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
+            formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
         }
         // Retry up to three times with a one second delay between the retries if the connection is closed.
-        Uni<HttpResponse<Buffer>> response = request.sendForm(reqBody)
+        Uni<HttpResponse<Buffer>> response = request.sendBuffer(OidcCommonUtils.encodeForm(formBody))
                 .onFailure(ConnectException.class)
                 .retry()
                 .atMost(3);