You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by "izumo27 (via GitHub)" <gi...@apache.org> on 2026/05/11 09:56:06 UTC

[PR] [improve][client] Implement tls_client_auth for AuthOauth2 [pulsar-client-cpp]

izumo27 opened a new pull request, #575:
URL: https://github.com/apache/pulsar-client-cpp/pull/575

   ### Motivation
   
   Currently, AuthOauth2 supports only `client_secret_post`.
   This PR adds `tls_client_auth`.
   cf. https://github.com/apache/pulsar/pull/25538
   
   ### Modifications
   
   - Allow configuration of `tls_cert_file` and `tls_key_file`, and make these two parameters required when using `tls_client_auth`.
   - Commonized the logic for setting the TLS context and getting tokens.
   
   ### Verifying this change
   
   - [X] Make sure that the change passes the CI checks.
     - https://github.com/izumo27/pulsar-client-cpp/pull/1
   
   *(Please pick either of the following options)*
   
   This change added tests and can be verified as follows:
   
   - Added tests for tls_client_auth
   - Implement a mock server for E2E tests
   
   ### Documentation
   
   <!-- DO NOT REMOVE THIS SECTION. CHECK THE PROPER BOX ONLY. -->
   
   - [x] `doc-required` 
   (Your PR needs to update docs and you will update later)
   
   - [ ] `doc-not-needed` 
   (Please explain why)
   
   - [ ] `doc` 
   (Your PR contains doc changes)
   
   - [ ] `doc-complete`
   (Docs have been already added)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@pulsar.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [improve][client] Implement tls_client_auth for AuthOauth2 [pulsar-client-cpp]

Posted by "izumo27 (via GitHub)" <gi...@apache.org>.
izumo27 commented on PR #575:
URL: https://github.com/apache/pulsar-client-cpp/pull/575#issuecomment-4539472959

   @BewareMyPower 
   Could you review this PR?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@pulsar.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [improve][client] Implement tls_client_auth for AuthOauth2 [pulsar-client-cpp]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #575:
URL: https://github.com/apache/pulsar-client-cpp/pull/575#discussion_r3218107294


##########
tests/AuthPluginTest.cc:
##########
@@ -518,6 +541,84 @@ TEST(AuthPluginTest, testAuthFactoryAthenz) {
     }
 }
 
+namespace testOauth2Tls {
+class MockOauth2Server {
+   public:
+    MockOauth2Server(const std::string& responseBody, const std::string& responseContentType, int listenPort,
+                     bool requireClientCert = true)
+        : responseBody_(responseBody),
+          responseContentType_(responseContentType),
+          acceptor_(io_, ASIO::ip::tcp::endpoint(ASIO::ip::tcp::v4(), static_cast<uint16_t>(listenPort))),
+          sslCtx_(ASIO::ssl::context::sslv23) {
+        sslCtx_.set_options(ASIO::ssl::context::default_workarounds | ASIO::ssl::context::no_sslv2 |
+                            ASIO::ssl::context::no_sslv3);
+        sslCtx_.use_certificate_chain_file(brokerPublicKeyPath);
+        sslCtx_.use_private_key_file(brokerPrivateKeyPath, ASIO::ssl::context::pem);
+        sslCtx_.load_verify_file(caPath);
+        sslCtx_.set_verify_mode(requireClientCert
+                                    ? (ASIO::ssl::verify_peer | ASIO::ssl::verify_fail_if_no_peer_cert)
+                                    : ASIO::ssl::verify_none);
+    }
+
+    const std::string& request() const { return request_; }
+
+    bool mockServe() {
+        ASIO::ip::tcp::socket socket(io_);
+        acceptor_.accept(socket);
+        ASIO_ERROR error;
+        ASIO::ssl::stream<ASIO::ip::tcp::socket&> sslStream(socket, sslCtx_);
+        sslStream.handshake(ASIO::ssl::stream_base::server, error);
+        if (error) return false;
+        if (!readRequest(sslStream)) return false;
+
+        const std::string response = "HTTP/1.1 200 OK\nContent-Type: " + responseContentType_ +
+                                     "\nContent-Length: " + std::to_string(responseBody_.size()) +
+                                     "\nConnection: close\n\n" + responseBody_;

Review Comment:
   The mock server returns HTTP headers separated by `\\n` rather than the HTTP-required `\\r\\n` (and header/body delimiter should be `\\r\\n\\r\\n`). Some HTTP clients (including libcurl depending on settings/build) can be strict here, leading to flaky tests. Use CRLF for all header lines and the header terminator.
   



##########
tests/AuthPluginTest.cc:
##########
@@ -668,6 +773,205 @@ TEST(AuthPluginTest, testOauth2Failure) {
     client5.close();
 }
 
+TEST(AuthPluginTest, testOauth2TlsClientAuth) {
+    const int tokenServerPort = 58081;
+    const int wellKnownServerPort = 58082;
+    const std::string tokenBody = R"({"access_token":"mockToken","expires_in":3600,"token_type":"Bearer"})";
+    std::unique_ptr<testOauth2Tls::MockOauth2Server> tokenServer;
+    try {
+        tokenServer =
+            std::make_unique<testOauth2Tls::MockOauth2Server>(tokenBody, "application/json", tokenServerPort);
+    } catch (const std::exception& e) {
+        FAIL() << "Failed to bind local mock token server: " << e.what();
+    }
+
+    std::promise<bool> tokenPromise;
+    auto tokenFuture = tokenPromise.get_future();
+    std::thread tokenThread(
+        [&tokenServer, &tokenPromise]() { tokenPromise.set_value(tokenServer->mockServe()); });
+
+    std::ostringstream wellKnownBody;
+    wellKnownBody << R"({"token_endpoint":"https://localhost:)" << tokenServerPort << R"(/oauth/token"})";
+    std::unique_ptr<testOauth2Tls::MockOauth2Server> wellKnownServer;
+    try {
+        wellKnownServer = std::make_unique<testOauth2Tls::MockOauth2Server>(
+            wellKnownBody.str(), "application/json", wellKnownServerPort, false);
+    } catch (const std::exception& e) {
+        tokenThread.join();
+        FAIL() << "Failed to bind local mock well-known server: " << e.what();
+    }
+
+    std::promise<bool> wellKnownPromise;
+    auto wellKnownFuture = wellKnownPromise.get_future();
+    std::thread wellKnownThread([&wellKnownServer, &wellKnownPromise]() {
+        wellKnownPromise.set_value(wellKnownServer->mockServe());
+    });
+
+    ParamMap params;
+    params["tokenEndpointAuthMethod"] = "tls_client_auth";
+    params["issuer_url"] = "https://localhost:" + std::to_string(wellKnownServerPort);
+    params["client_id"] = "test-client";
+    params["tls_cert_file"] = clientPublicKeyPath;
+    params["tls_key_file"] = clientPrivateKeyPath;
+
+    AuthenticationDataPtr data =
+        std::static_pointer_cast<AuthenticationDataProvider>(std::make_shared<InitialAuthData>(caPath));
+    AuthenticationPtr auth = AuthOauth2::create(params);
+    ASSERT_EQ(auth->getAuthData(data), ResultOk);
+    ASSERT_TRUE(data->hasDataFromCommand());
+    ASSERT_EQ(data->getCommandData(), "mockToken");
+
+    ASSERT_TRUE(wellKnownFuture.get());
+    ASSERT_TRUE(tokenFuture.get());

Review Comment:
   These tests can hang indefinitely if the mock server thread blocks (e.g., accept/handshake/read never completes) because `future.get()` has no timeout and the threads are joined only afterward. To prevent CI deadlocks, add bounded waits (e.g., `wait_for` with a reasonable timeout) and fail the test if the server doesn’t complete in time (optionally closing the acceptor/socket to unblock).



##########
tests/AuthPluginTest.cc:
##########
@@ -668,6 +773,205 @@ TEST(AuthPluginTest, testOauth2Failure) {
     client5.close();
 }
 
+TEST(AuthPluginTest, testOauth2TlsClientAuth) {
+    const int tokenServerPort = 58081;
+    const int wellKnownServerPort = 58082;
+    const std::string tokenBody = R"({"access_token":"mockToken","expires_in":3600,"token_type":"Bearer"})";
+    std::unique_ptr<testOauth2Tls::MockOauth2Server> tokenServer;
+    try {
+        tokenServer =
+            std::make_unique<testOauth2Tls::MockOauth2Server>(tokenBody, "application/json", tokenServerPort);
+    } catch (const std::exception& e) {
+        FAIL() << "Failed to bind local mock token server: " << e.what();
+    }
+
+    std::promise<bool> tokenPromise;
+    auto tokenFuture = tokenPromise.get_future();
+    std::thread tokenThread(
+        [&tokenServer, &tokenPromise]() { tokenPromise.set_value(tokenServer->mockServe()); });
+
+    std::ostringstream wellKnownBody;
+    wellKnownBody << R"({"token_endpoint":"https://localhost:)" << tokenServerPort << R"(/oauth/token"})";
+    std::unique_ptr<testOauth2Tls::MockOauth2Server> wellKnownServer;
+    try {
+        wellKnownServer = std::make_unique<testOauth2Tls::MockOauth2Server>(
+            wellKnownBody.str(), "application/json", wellKnownServerPort, false);
+    } catch (const std::exception& e) {
+        tokenThread.join();
+        FAIL() << "Failed to bind local mock well-known server: " << e.what();
+    }
+
+    std::promise<bool> wellKnownPromise;
+    auto wellKnownFuture = wellKnownPromise.get_future();
+    std::thread wellKnownThread([&wellKnownServer, &wellKnownPromise]() {
+        wellKnownPromise.set_value(wellKnownServer->mockServe());
+    });
+
+    ParamMap params;
+    params["tokenEndpointAuthMethod"] = "tls_client_auth";
+    params["issuer_url"] = "https://localhost:" + std::to_string(wellKnownServerPort);
+    params["client_id"] = "test-client";
+    params["tls_cert_file"] = clientPublicKeyPath;
+    params["tls_key_file"] = clientPrivateKeyPath;
+
+    AuthenticationDataPtr data =
+        std::static_pointer_cast<AuthenticationDataProvider>(std::make_shared<InitialAuthData>(caPath));
+    AuthenticationPtr auth = AuthOauth2::create(params);
+    ASSERT_EQ(auth->getAuthData(data), ResultOk);
+    ASSERT_TRUE(data->hasDataFromCommand());
+    ASSERT_EQ(data->getCommandData(), "mockToken");
+
+    ASSERT_TRUE(wellKnownFuture.get());
+    ASSERT_TRUE(tokenFuture.get());
+    ASSERT_NE(wellKnownServer->request().find("GET /.well-known/openid-configuration "), std::string::npos);
+    ASSERT_NE(tokenServer->request().find("POST /oauth/token "), std::string::npos);
+    ASSERT_NE(tokenServer->request().find("grant_type=client_credentials"), std::string::npos);
+    wellKnownThread.join();
+    tokenThread.join();

Review Comment:
   These tests can hang indefinitely if the mock server thread blocks (e.g., accept/handshake/read never completes) because `future.get()` has no timeout and the threads are joined only afterward. To prevent CI deadlocks, add bounded waits (e.g., `wait_for` with a reasonable timeout) and fail the test if the server doesn’t complete in time (optionally closing the acceptor/socket to unblock).



##########
lib/auth/AuthOauth2.cc:
##########
@@ -199,80 +232,186 @@ KeyFile KeyFile::fromBase64(const std::string& encoded) {
     }
 }
 
-ClientCredentialFlow::ClientCredentialFlow(ParamMap& params)
-    : issuerUrl_(params["issuer_url"]),
-      keyFile_(KeyFile::fromParamMap(params)),
-      audience_(params["audience"]),
-      scope_(params["scope"]) {}
+static std::string getWellKnownUrl(const std::string& issuerUrl) {
+    std::string wellKnownUrl = issuerUrl;
+    if (!wellKnownUrl.empty() && wellKnownUrl.back() == '/') {
+        wellKnownUrl.pop_back();
+    }
+    wellKnownUrl.append("/.well-known/openid-configuration");
+    return wellKnownUrl;
+}
 
-std::string ClientCredentialFlow::getTokenEndPoint() const { return tokenEndPoint_; }
+static std::unique_ptr<CurlWrapper::TlsContext> createTlsContext(const std::string& tlsTrustCertsFilePath,
+                                                                 const std::string& tlsCertFilePath,
+                                                                 const std::string& tlsKeyFilePath,
+                                                                 OAuth2TokenEndpointAuthMethod authMethod) {
+    if (tlsTrustCertsFilePath.empty() && tlsCertFilePath.empty() && tlsKeyFilePath.empty()) {
+        return nullptr;
+    }
 
-void ClientCredentialFlow::initialize() {
-    if (issuerUrl_.empty()) {
-        LOG_ERROR("Failed to initialize ClientCredentialFlow: issuer_url is not set");
-        return;
+    auto tlsContext = std::unique_ptr<CurlWrapper::TlsContext>(new CurlWrapper::TlsContext);
+    if (!tlsTrustCertsFilePath.empty()) {
+        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath;
     }
-    if (!keyFile_.isValid()) {
-        return;
+    if (!tlsCertFilePath.empty() && !tlsKeyFilePath.empty()) {
+        tlsContext->certPath = tlsCertFilePath;
+        tlsContext->keyPath = tlsKeyFilePath;
+    } else if (authMethod == OAuth2TokenEndpointAuthMethod::TlsClientAuth) {
+        LOG_WARN("Ignore incomplete mTLS settings: both tls_cert_file and tls_key_file are required");
     }
+    return tlsContext;
+}
 
-    // set URL: well-know endpoint
-    std::string wellKnownUrl = issuerUrl_;
-    if (wellKnownUrl.back() == '/') {
-        wellKnownUrl.pop_back();
+static std::string fetchTokenEndpoint(const std::string& issuerUrl,
+                                      const CurlWrapper::TlsContext* tlsContext) {
+    const auto wellKnownUrl = getWellKnownUrl(issuerUrl);
+    CurlWrapper curl;
+    if (!curl.init()) {
+        LOG_ERROR("Failed to initialize curl");
+        return "";
+    }
+
+    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext);
+    if (!result.error.empty()) {
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return "";
+    }
+
+    const auto res = result.code;
+    const auto responseCode = result.responseCode;
+    const auto& responseData = result.responseData;
+    const auto& errorBuffer = result.serverError;
+
+    switch (res) {
+        case CURLE_OK:
+            LOG_DEBUG("Received well-known configuration data " << issuerUrl << " code " << responseCode);
+            if (responseCode == 200) {
+                boost::property_tree::ptree root;
+                std::stringstream stream;
+                stream << responseData;
+                try {
+                    boost::property_tree::read_json(stream, root);
+                    return root.get<std::string>("token_endpoint");
+                } catch (boost::property_tree::json_parser_error& e) {
+                    LOG_ERROR("Failed to parse well-known configuration data response: "
+                              << e.what() << "\nInput Json = " << responseData);
+                    return "";
+                }
+            } else {
+                LOG_ERROR("Response failed for getting the well-known configuration "
+                          << issuerUrl << ". response Code " << responseCode);
+            }
+            break;
+        default:
+            LOG_ERROR("Response failed for getting the well-known configuration "
+                      << issuerUrl << ". Error Code " << res << ": " << errorBuffer);
+            break;
+    }
+    return "";
+}
+
+static Oauth2TokenResultPtr fetchOauth2Token(const std::string& issuerUrl, const std::string& tokenEndpoint,
+                                             const ParamMap& params,
+                                             const CurlWrapper::TlsContext* tlsContext,
+                                             OAuth2TokenEndpointAuthMethod authMethod) {
+    Oauth2TokenResultPtr resultPtr = Oauth2TokenResultPtr(new Oauth2TokenResult());
+    if (tokenEndpoint.empty()) {
+        return resultPtr;
     }
-    wellKnownUrl.append("/.well-known/openid-configuration");
 
     CurlWrapper curl;
     if (!curl.init()) {
         LOG_ERROR("Failed to initialize curl");
-        return;
+        return resultPtr;
     }
-    std::unique_ptr<CurlWrapper::TlsContext> tlsContext;
-    if (!tlsTrustCertsFilePath_.empty()) {
-        tlsContext.reset(new CurlWrapper::TlsContext);
-        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_;
+
+    auto postData = buildClientCredentialsBody(curl, params);
+    if (postData.empty()) {
+        return resultPtr;
     }
+    LOG_DEBUG("Generate URL encoded body for " << toFlowName(authMethod) << ": " << postData);
 
-    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext.get());
+    CurlWrapper::Options options;
+    options.postFields = std::move(postData);
+    auto result =
+        curl.get(tokenEndpoint, "Content-Type: application/x-www-form-urlencoded", options, tlsContext);
     if (!result.error.empty()) {
-        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl_ << ": " << result.error);
-        return;
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return resultPtr;
     }

Review Comment:
   This error message is misleading in `fetchOauth2Token()`: the request here targets the token endpoint, not the well-known configuration. Please update the message to indicate token retrieval failure (and ideally include the token endpoint URL rather than `issuerUrl`).



##########
lib/auth/AuthOauth2.cc:
##########
@@ -199,80 +232,186 @@ KeyFile KeyFile::fromBase64(const std::string& encoded) {
     }
 }
 
-ClientCredentialFlow::ClientCredentialFlow(ParamMap& params)
-    : issuerUrl_(params["issuer_url"]),
-      keyFile_(KeyFile::fromParamMap(params)),
-      audience_(params["audience"]),
-      scope_(params["scope"]) {}
+static std::string getWellKnownUrl(const std::string& issuerUrl) {
+    std::string wellKnownUrl = issuerUrl;
+    if (!wellKnownUrl.empty() && wellKnownUrl.back() == '/') {
+        wellKnownUrl.pop_back();
+    }
+    wellKnownUrl.append("/.well-known/openid-configuration");
+    return wellKnownUrl;
+}
 
-std::string ClientCredentialFlow::getTokenEndPoint() const { return tokenEndPoint_; }
+static std::unique_ptr<CurlWrapper::TlsContext> createTlsContext(const std::string& tlsTrustCertsFilePath,
+                                                                 const std::string& tlsCertFilePath,
+                                                                 const std::string& tlsKeyFilePath,
+                                                                 OAuth2TokenEndpointAuthMethod authMethod) {
+    if (tlsTrustCertsFilePath.empty() && tlsCertFilePath.empty() && tlsKeyFilePath.empty()) {
+        return nullptr;
+    }
 
-void ClientCredentialFlow::initialize() {
-    if (issuerUrl_.empty()) {
-        LOG_ERROR("Failed to initialize ClientCredentialFlow: issuer_url is not set");
-        return;
+    auto tlsContext = std::unique_ptr<CurlWrapper::TlsContext>(new CurlWrapper::TlsContext);
+    if (!tlsTrustCertsFilePath.empty()) {
+        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath;
     }
-    if (!keyFile_.isValid()) {
-        return;
+    if (!tlsCertFilePath.empty() && !tlsKeyFilePath.empty()) {
+        tlsContext->certPath = tlsCertFilePath;
+        tlsContext->keyPath = tlsKeyFilePath;
+    } else if (authMethod == OAuth2TokenEndpointAuthMethod::TlsClientAuth) {
+        LOG_WARN("Ignore incomplete mTLS settings: both tls_cert_file and tls_key_file are required");
     }
+    return tlsContext;
+}

Review Comment:
   If only one of `tls_cert_file`/`tls_key_file` is set (and `tlsTrustCertsFilePath` is empty), this returns a non-null `TlsContext` with all-empty fields. Depending on how `CurlWrapper` applies options, passing a non-null-but-empty TLS context can change behavior or even break requests. Consider only allocating/returning a context when at least one concrete field is actually set (trust certs, or both cert+key); otherwise return `nullptr` (and optionally warn for incomplete mTLS regardless of auth method).



##########
lib/auth/AuthOauth2.cc:
##########
@@ -199,80 +232,186 @@ KeyFile KeyFile::fromBase64(const std::string& encoded) {
     }
 }
 
-ClientCredentialFlow::ClientCredentialFlow(ParamMap& params)
-    : issuerUrl_(params["issuer_url"]),
-      keyFile_(KeyFile::fromParamMap(params)),
-      audience_(params["audience"]),
-      scope_(params["scope"]) {}
+static std::string getWellKnownUrl(const std::string& issuerUrl) {
+    std::string wellKnownUrl = issuerUrl;
+    if (!wellKnownUrl.empty() && wellKnownUrl.back() == '/') {
+        wellKnownUrl.pop_back();
+    }
+    wellKnownUrl.append("/.well-known/openid-configuration");
+    return wellKnownUrl;
+}
 
-std::string ClientCredentialFlow::getTokenEndPoint() const { return tokenEndPoint_; }
+static std::unique_ptr<CurlWrapper::TlsContext> createTlsContext(const std::string& tlsTrustCertsFilePath,
+                                                                 const std::string& tlsCertFilePath,
+                                                                 const std::string& tlsKeyFilePath,
+                                                                 OAuth2TokenEndpointAuthMethod authMethod) {
+    if (tlsTrustCertsFilePath.empty() && tlsCertFilePath.empty() && tlsKeyFilePath.empty()) {
+        return nullptr;
+    }
 
-void ClientCredentialFlow::initialize() {
-    if (issuerUrl_.empty()) {
-        LOG_ERROR("Failed to initialize ClientCredentialFlow: issuer_url is not set");
-        return;
+    auto tlsContext = std::unique_ptr<CurlWrapper::TlsContext>(new CurlWrapper::TlsContext);
+    if (!tlsTrustCertsFilePath.empty()) {
+        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath;
     }
-    if (!keyFile_.isValid()) {
-        return;
+    if (!tlsCertFilePath.empty() && !tlsKeyFilePath.empty()) {
+        tlsContext->certPath = tlsCertFilePath;
+        tlsContext->keyPath = tlsKeyFilePath;
+    } else if (authMethod == OAuth2TokenEndpointAuthMethod::TlsClientAuth) {
+        LOG_WARN("Ignore incomplete mTLS settings: both tls_cert_file and tls_key_file are required");
     }
+    return tlsContext;
+}
 
-    // set URL: well-know endpoint
-    std::string wellKnownUrl = issuerUrl_;
-    if (wellKnownUrl.back() == '/') {
-        wellKnownUrl.pop_back();
+static std::string fetchTokenEndpoint(const std::string& issuerUrl,
+                                      const CurlWrapper::TlsContext* tlsContext) {
+    const auto wellKnownUrl = getWellKnownUrl(issuerUrl);
+    CurlWrapper curl;
+    if (!curl.init()) {
+        LOG_ERROR("Failed to initialize curl");
+        return "";
+    }
+
+    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext);
+    if (!result.error.empty()) {
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return "";
+    }
+
+    const auto res = result.code;
+    const auto responseCode = result.responseCode;
+    const auto& responseData = result.responseData;
+    const auto& errorBuffer = result.serverError;
+
+    switch (res) {
+        case CURLE_OK:
+            LOG_DEBUG("Received well-known configuration data " << issuerUrl << " code " << responseCode);
+            if (responseCode == 200) {
+                boost::property_tree::ptree root;
+                std::stringstream stream;
+                stream << responseData;
+                try {
+                    boost::property_tree::read_json(stream, root);
+                    return root.get<std::string>("token_endpoint");
+                } catch (boost::property_tree::json_parser_error& e) {
+                    LOG_ERROR("Failed to parse well-known configuration data response: "
+                              << e.what() << "\nInput Json = " << responseData);
+                    return "";
+                }
+            } else {
+                LOG_ERROR("Response failed for getting the well-known configuration "
+                          << issuerUrl << ". response Code " << responseCode);
+            }
+            break;
+        default:
+            LOG_ERROR("Response failed for getting the well-known configuration "
+                      << issuerUrl << ". Error Code " << res << ": " << errorBuffer);
+            break;
+    }
+    return "";
+}
+
+static Oauth2TokenResultPtr fetchOauth2Token(const std::string& issuerUrl, const std::string& tokenEndpoint,
+                                             const ParamMap& params,
+                                             const CurlWrapper::TlsContext* tlsContext,
+                                             OAuth2TokenEndpointAuthMethod authMethod) {
+    Oauth2TokenResultPtr resultPtr = Oauth2TokenResultPtr(new Oauth2TokenResult());
+    if (tokenEndpoint.empty()) {
+        return resultPtr;
     }
-    wellKnownUrl.append("/.well-known/openid-configuration");
 
     CurlWrapper curl;
     if (!curl.init()) {
         LOG_ERROR("Failed to initialize curl");
-        return;
+        return resultPtr;
     }
-    std::unique_ptr<CurlWrapper::TlsContext> tlsContext;
-    if (!tlsTrustCertsFilePath_.empty()) {
-        tlsContext.reset(new CurlWrapper::TlsContext);
-        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_;
+
+    auto postData = buildClientCredentialsBody(curl, params);
+    if (postData.empty()) {
+        return resultPtr;
     }
+    LOG_DEBUG("Generate URL encoded body for " << toFlowName(authMethod) << ": " << postData);
 
-    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext.get());
+    CurlWrapper::Options options;
+    options.postFields = std::move(postData);
+    auto result =
+        curl.get(tokenEndpoint, "Content-Type: application/x-www-form-urlencoded", options, tlsContext);
     if (!result.error.empty()) {
-        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl_ << ": " << result.error);
-        return;
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return resultPtr;
     }
 
     const auto res = result.code;
-    const auto response_code = result.responseCode;
+    const auto responseCode = result.responseCode;
     const auto& responseData = result.responseData;
     const auto& errorBuffer = result.serverError;
 
     switch (res) {
         case CURLE_OK:
-            LOG_DEBUG("Received well-known configuration data " << issuerUrl_ << " code " << response_code);
-            if (response_code == 200) {
+            LOG_DEBUG("Response received for issuerurl " << issuerUrl << " code " << responseCode);
+            if (responseCode == 200) {
                 boost::property_tree::ptree root;
                 std::stringstream stream;
                 stream << responseData;
                 try {
                     boost::property_tree::read_json(stream, root);
                 } catch (boost::property_tree::json_parser_error& e) {
-                    LOG_ERROR("Failed to parse well-known configuration data response: "
-                              << e.what() << "\nInput Json = " << responseData);
+                    LOG_ERROR("Failed to parse json of Oauth2 response: "
+                              << e.what() << "\nInput Json = " << responseData
+                              << " passedin: " << options.postFields);
                     break;
                 }
 
-                this->tokenEndPoint_ = root.get<std::string>("token_endpoint");
+                resultPtr->setAccessToken(root.get<std::string>("access_token", ""));
+                resultPtr->setExpiresIn(
+                    root.get<uint32_t>("expires_in", Oauth2TokenResult::undefined_expiration));
+                resultPtr->setRefreshToken(root.get<std::string>("refresh_token", ""));
+                resultPtr->setIdToken(root.get<std::string>("id_token", ""));
 
-                LOG_DEBUG("Get token endpoint: " << this->tokenEndPoint_);
+                if (!resultPtr->getAccessToken().empty()) {
+                    LOG_DEBUG("access_token: " << resultPtr->getAccessToken()
+                                               << " expires_in: " << resultPtr->getExpiresIn());
+                } else {
+                    LOG_ERROR("Response doesn't contain access_token, the response is: " << responseData);
+                }
             } else {
-                LOG_ERROR("Response failed for getting the well-known configuration "
-                          << issuerUrl_ << ". response Code " << response_code);
+                LOG_ERROR("Response failed for issuerurl " << issuerUrl << ". response Code " << responseCode
+                                                           << " passedin: " << options.postFields);
             }
             break;
         default:
-            LOG_ERROR("Response failed for getting the well-known configuration "
-                      << issuerUrl_ << ". Error Code " << res << ": " << errorBuffer);
+            LOG_ERROR("Response failed for issuerurl " << issuerUrl << ". ErrorCode " << res << ": "
+                                                       << errorBuffer << " passedin: " << options.postFields);

Review Comment:
   Logging `options.postFields` risks leaking sensitive credentials (e.g., `client_secret`, assertions, etc.) into logs—especially problematic at `LOG_ERROR` where logs are more likely to be retained/forwarded. Please remove `passedin: ...` from error logs or redact sensitive parameters before logging (e.g., log only parameter names, or mask known sensitive keys).



##########
lib/auth/AuthOauth2.cc:
##########
@@ -199,80 +232,186 @@ KeyFile KeyFile::fromBase64(const std::string& encoded) {
     }
 }
 
-ClientCredentialFlow::ClientCredentialFlow(ParamMap& params)
-    : issuerUrl_(params["issuer_url"]),
-      keyFile_(KeyFile::fromParamMap(params)),
-      audience_(params["audience"]),
-      scope_(params["scope"]) {}
+static std::string getWellKnownUrl(const std::string& issuerUrl) {
+    std::string wellKnownUrl = issuerUrl;
+    if (!wellKnownUrl.empty() && wellKnownUrl.back() == '/') {
+        wellKnownUrl.pop_back();
+    }
+    wellKnownUrl.append("/.well-known/openid-configuration");
+    return wellKnownUrl;
+}
 
-std::string ClientCredentialFlow::getTokenEndPoint() const { return tokenEndPoint_; }
+static std::unique_ptr<CurlWrapper::TlsContext> createTlsContext(const std::string& tlsTrustCertsFilePath,
+                                                                 const std::string& tlsCertFilePath,
+                                                                 const std::string& tlsKeyFilePath,
+                                                                 OAuth2TokenEndpointAuthMethod authMethod) {
+    if (tlsTrustCertsFilePath.empty() && tlsCertFilePath.empty() && tlsKeyFilePath.empty()) {
+        return nullptr;
+    }
 
-void ClientCredentialFlow::initialize() {
-    if (issuerUrl_.empty()) {
-        LOG_ERROR("Failed to initialize ClientCredentialFlow: issuer_url is not set");
-        return;
+    auto tlsContext = std::unique_ptr<CurlWrapper::TlsContext>(new CurlWrapper::TlsContext);
+    if (!tlsTrustCertsFilePath.empty()) {
+        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath;
     }
-    if (!keyFile_.isValid()) {
-        return;
+    if (!tlsCertFilePath.empty() && !tlsKeyFilePath.empty()) {
+        tlsContext->certPath = tlsCertFilePath;
+        tlsContext->keyPath = tlsKeyFilePath;
+    } else if (authMethod == OAuth2TokenEndpointAuthMethod::TlsClientAuth) {
+        LOG_WARN("Ignore incomplete mTLS settings: both tls_cert_file and tls_key_file are required");
     }
+    return tlsContext;
+}
 
-    // set URL: well-know endpoint
-    std::string wellKnownUrl = issuerUrl_;
-    if (wellKnownUrl.back() == '/') {
-        wellKnownUrl.pop_back();
+static std::string fetchTokenEndpoint(const std::string& issuerUrl,
+                                      const CurlWrapper::TlsContext* tlsContext) {
+    const auto wellKnownUrl = getWellKnownUrl(issuerUrl);
+    CurlWrapper curl;
+    if (!curl.init()) {
+        LOG_ERROR("Failed to initialize curl");
+        return "";
+    }
+
+    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext);
+    if (!result.error.empty()) {
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return "";
+    }
+
+    const auto res = result.code;
+    const auto responseCode = result.responseCode;
+    const auto& responseData = result.responseData;
+    const auto& errorBuffer = result.serverError;
+
+    switch (res) {
+        case CURLE_OK:
+            LOG_DEBUG("Received well-known configuration data " << issuerUrl << " code " << responseCode);
+            if (responseCode == 200) {
+                boost::property_tree::ptree root;
+                std::stringstream stream;
+                stream << responseData;
+                try {
+                    boost::property_tree::read_json(stream, root);
+                    return root.get<std::string>("token_endpoint");
+                } catch (boost::property_tree::json_parser_error& e) {
+                    LOG_ERROR("Failed to parse well-known configuration data response: "
+                              << e.what() << "\nInput Json = " << responseData);
+                    return "";
+                }
+            } else {
+                LOG_ERROR("Response failed for getting the well-known configuration "
+                          << issuerUrl << ". response Code " << responseCode);
+            }
+            break;
+        default:
+            LOG_ERROR("Response failed for getting the well-known configuration "
+                      << issuerUrl << ". Error Code " << res << ": " << errorBuffer);
+            break;
+    }
+    return "";
+}
+
+static Oauth2TokenResultPtr fetchOauth2Token(const std::string& issuerUrl, const std::string& tokenEndpoint,
+                                             const ParamMap& params,
+                                             const CurlWrapper::TlsContext* tlsContext,
+                                             OAuth2TokenEndpointAuthMethod authMethod) {
+    Oauth2TokenResultPtr resultPtr = Oauth2TokenResultPtr(new Oauth2TokenResult());
+    if (tokenEndpoint.empty()) {
+        return resultPtr;
     }
-    wellKnownUrl.append("/.well-known/openid-configuration");
 
     CurlWrapper curl;
     if (!curl.init()) {
         LOG_ERROR("Failed to initialize curl");
-        return;
+        return resultPtr;
     }
-    std::unique_ptr<CurlWrapper::TlsContext> tlsContext;
-    if (!tlsTrustCertsFilePath_.empty()) {
-        tlsContext.reset(new CurlWrapper::TlsContext);
-        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_;
+
+    auto postData = buildClientCredentialsBody(curl, params);
+    if (postData.empty()) {
+        return resultPtr;
     }
+    LOG_DEBUG("Generate URL encoded body for " << toFlowName(authMethod) << ": " << postData);
 
-    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext.get());
+    CurlWrapper::Options options;
+    options.postFields = std::move(postData);
+    auto result =
+        curl.get(tokenEndpoint, "Content-Type: application/x-www-form-urlencoded", options, tlsContext);
     if (!result.error.empty()) {
-        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl_ << ": " << result.error);
-        return;
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return resultPtr;
     }
 
     const auto res = result.code;
-    const auto response_code = result.responseCode;
+    const auto responseCode = result.responseCode;
     const auto& responseData = result.responseData;
     const auto& errorBuffer = result.serverError;
 
     switch (res) {
         case CURLE_OK:
-            LOG_DEBUG("Received well-known configuration data " << issuerUrl_ << " code " << response_code);
-            if (response_code == 200) {
+            LOG_DEBUG("Response received for issuerurl " << issuerUrl << " code " << responseCode);
+            if (responseCode == 200) {
                 boost::property_tree::ptree root;
                 std::stringstream stream;
                 stream << responseData;
                 try {
                     boost::property_tree::read_json(stream, root);
                 } catch (boost::property_tree::json_parser_error& e) {
-                    LOG_ERROR("Failed to parse well-known configuration data response: "
-                              << e.what() << "\nInput Json = " << responseData);
+                    LOG_ERROR("Failed to parse json of Oauth2 response: "
+                              << e.what() << "\nInput Json = " << responseData
+                              << " passedin: " << options.postFields);

Review Comment:
   Logging `options.postFields` risks leaking sensitive credentials (e.g., `client_secret`, assertions, etc.) into logs—especially problematic at `LOG_ERROR` where logs are more likely to be retained/forwarded. Please remove `passedin: ...` from error logs or redact sensitive parameters before logging (e.g., log only parameter names, or mask known sensitive keys).



##########
lib/auth/AuthOauth2.cc:
##########
@@ -199,80 +232,186 @@ KeyFile KeyFile::fromBase64(const std::string& encoded) {
     }
 }
 
-ClientCredentialFlow::ClientCredentialFlow(ParamMap& params)
-    : issuerUrl_(params["issuer_url"]),
-      keyFile_(KeyFile::fromParamMap(params)),
-      audience_(params["audience"]),
-      scope_(params["scope"]) {}
+static std::string getWellKnownUrl(const std::string& issuerUrl) {
+    std::string wellKnownUrl = issuerUrl;
+    if (!wellKnownUrl.empty() && wellKnownUrl.back() == '/') {
+        wellKnownUrl.pop_back();
+    }
+    wellKnownUrl.append("/.well-known/openid-configuration");
+    return wellKnownUrl;
+}
 
-std::string ClientCredentialFlow::getTokenEndPoint() const { return tokenEndPoint_; }
+static std::unique_ptr<CurlWrapper::TlsContext> createTlsContext(const std::string& tlsTrustCertsFilePath,
+                                                                 const std::string& tlsCertFilePath,
+                                                                 const std::string& tlsKeyFilePath,
+                                                                 OAuth2TokenEndpointAuthMethod authMethod) {
+    if (tlsTrustCertsFilePath.empty() && tlsCertFilePath.empty() && tlsKeyFilePath.empty()) {
+        return nullptr;
+    }
 
-void ClientCredentialFlow::initialize() {
-    if (issuerUrl_.empty()) {
-        LOG_ERROR("Failed to initialize ClientCredentialFlow: issuer_url is not set");
-        return;
+    auto tlsContext = std::unique_ptr<CurlWrapper::TlsContext>(new CurlWrapper::TlsContext);
+    if (!tlsTrustCertsFilePath.empty()) {
+        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath;
     }
-    if (!keyFile_.isValid()) {
-        return;
+    if (!tlsCertFilePath.empty() && !tlsKeyFilePath.empty()) {
+        tlsContext->certPath = tlsCertFilePath;
+        tlsContext->keyPath = tlsKeyFilePath;
+    } else if (authMethod == OAuth2TokenEndpointAuthMethod::TlsClientAuth) {
+        LOG_WARN("Ignore incomplete mTLS settings: both tls_cert_file and tls_key_file are required");
     }
+    return tlsContext;
+}
 
-    // set URL: well-know endpoint
-    std::string wellKnownUrl = issuerUrl_;
-    if (wellKnownUrl.back() == '/') {
-        wellKnownUrl.pop_back();
+static std::string fetchTokenEndpoint(const std::string& issuerUrl,
+                                      const CurlWrapper::TlsContext* tlsContext) {
+    const auto wellKnownUrl = getWellKnownUrl(issuerUrl);
+    CurlWrapper curl;
+    if (!curl.init()) {
+        LOG_ERROR("Failed to initialize curl");
+        return "";
+    }
+
+    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext);
+    if (!result.error.empty()) {
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return "";
+    }
+
+    const auto res = result.code;
+    const auto responseCode = result.responseCode;
+    const auto& responseData = result.responseData;
+    const auto& errorBuffer = result.serverError;
+
+    switch (res) {
+        case CURLE_OK:
+            LOG_DEBUG("Received well-known configuration data " << issuerUrl << " code " << responseCode);
+            if (responseCode == 200) {
+                boost::property_tree::ptree root;
+                std::stringstream stream;
+                stream << responseData;
+                try {
+                    boost::property_tree::read_json(stream, root);
+                    return root.get<std::string>("token_endpoint");
+                } catch (boost::property_tree::json_parser_error& e) {
+                    LOG_ERROR("Failed to parse well-known configuration data response: "
+                              << e.what() << "\nInput Json = " << responseData);
+                    return "";
+                }
+            } else {
+                LOG_ERROR("Response failed for getting the well-known configuration "
+                          << issuerUrl << ". response Code " << responseCode);
+            }
+            break;
+        default:
+            LOG_ERROR("Response failed for getting the well-known configuration "
+                      << issuerUrl << ". Error Code " << res << ": " << errorBuffer);
+            break;
+    }
+    return "";
+}
+
+static Oauth2TokenResultPtr fetchOauth2Token(const std::string& issuerUrl, const std::string& tokenEndpoint,
+                                             const ParamMap& params,
+                                             const CurlWrapper::TlsContext* tlsContext,
+                                             OAuth2TokenEndpointAuthMethod authMethod) {
+    Oauth2TokenResultPtr resultPtr = Oauth2TokenResultPtr(new Oauth2TokenResult());
+    if (tokenEndpoint.empty()) {
+        return resultPtr;
     }
-    wellKnownUrl.append("/.well-known/openid-configuration");
 
     CurlWrapper curl;
     if (!curl.init()) {
         LOG_ERROR("Failed to initialize curl");
-        return;
+        return resultPtr;
     }
-    std::unique_ptr<CurlWrapper::TlsContext> tlsContext;
-    if (!tlsTrustCertsFilePath_.empty()) {
-        tlsContext.reset(new CurlWrapper::TlsContext);
-        tlsContext->trustCertsFilePath = tlsTrustCertsFilePath_;
+
+    auto postData = buildClientCredentialsBody(curl, params);
+    if (postData.empty()) {
+        return resultPtr;
     }
+    LOG_DEBUG("Generate URL encoded body for " << toFlowName(authMethod) << ": " << postData);
 
-    auto result = curl.get(wellKnownUrl, "Accept: application/json", {}, tlsContext.get());
+    CurlWrapper::Options options;
+    options.postFields = std::move(postData);
+    auto result =
+        curl.get(tokenEndpoint, "Content-Type: application/x-www-form-urlencoded", options, tlsContext);
     if (!result.error.empty()) {
-        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl_ << ": " << result.error);
-        return;
+        LOG_ERROR("Failed to get the well-known configuration " << issuerUrl << ": " << result.error);
+        return resultPtr;
     }
 
     const auto res = result.code;
-    const auto response_code = result.responseCode;
+    const auto responseCode = result.responseCode;
     const auto& responseData = result.responseData;
     const auto& errorBuffer = result.serverError;
 
     switch (res) {
         case CURLE_OK:
-            LOG_DEBUG("Received well-known configuration data " << issuerUrl_ << " code " << response_code);
-            if (response_code == 200) {
+            LOG_DEBUG("Response received for issuerurl " << issuerUrl << " code " << responseCode);
+            if (responseCode == 200) {
                 boost::property_tree::ptree root;
                 std::stringstream stream;
                 stream << responseData;
                 try {
                     boost::property_tree::read_json(stream, root);
                 } catch (boost::property_tree::json_parser_error& e) {
-                    LOG_ERROR("Failed to parse well-known configuration data response: "
-                              << e.what() << "\nInput Json = " << responseData);
+                    LOG_ERROR("Failed to parse json of Oauth2 response: "
+                              << e.what() << "\nInput Json = " << responseData
+                              << " passedin: " << options.postFields);
                     break;
                 }
 
-                this->tokenEndPoint_ = root.get<std::string>("token_endpoint");
+                resultPtr->setAccessToken(root.get<std::string>("access_token", ""));
+                resultPtr->setExpiresIn(
+                    root.get<uint32_t>("expires_in", Oauth2TokenResult::undefined_expiration));
+                resultPtr->setRefreshToken(root.get<std::string>("refresh_token", ""));
+                resultPtr->setIdToken(root.get<std::string>("id_token", ""));
 
-                LOG_DEBUG("Get token endpoint: " << this->tokenEndPoint_);
+                if (!resultPtr->getAccessToken().empty()) {
+                    LOG_DEBUG("access_token: " << resultPtr->getAccessToken()
+                                               << " expires_in: " << resultPtr->getExpiresIn());
+                } else {
+                    LOG_ERROR("Response doesn't contain access_token, the response is: " << responseData);
+                }
             } else {
-                LOG_ERROR("Response failed for getting the well-known configuration "
-                          << issuerUrl_ << ". response Code " << response_code);
+                LOG_ERROR("Response failed for issuerurl " << issuerUrl << ". response Code " << responseCode
+                                                           << " passedin: " << options.postFields);

Review Comment:
   Logging `options.postFields` risks leaking sensitive credentials (e.g., `client_secret`, assertions, etc.) into logs—especially problematic at `LOG_ERROR` where logs are more likely to be retained/forwarded. Please remove `passedin: ...` from error logs or redact sensitive parameters before logging (e.g., log only parameter names, or mask known sensitive keys).



##########
include/pulsar/Authentication.h:
##########
@@ -515,11 +515,22 @@ typedef std::shared_ptr<CachedToken> CachedTokenPtr;
  * Passed in parameter would be like:
  * ```
  *   "type": "client_credentials",
+ *   "tokenEndpointAuthMethod": "client_secret_post",
  *   "issuer_url": "https://accounts.google.com",
  *   "client_id": "d9ZyX97q1ef8Cr81WHVC4hFQ64vSlDK3",
  *   "client_secret": "on1uJ...k6F6R",

Review Comment:
   The doc example uses `client_secret`, but the constructor documentation below states the required key is `private_key` for `client_secret_post`, and the implementation uses `KeyFile::fromParamMap()` which expects `private_key`. Please align the example and the documented required keys with the actual supported parameters to avoid confusing API consumers.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@pulsar.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [improve][client] Implement tls_client_auth for AuthOauth2 [pulsar-client-cpp]

Posted by "izumo27 (via GitHub)" <gi...@apache.org>.
izumo27 commented on code in PR #575:
URL: https://github.com/apache/pulsar-client-cpp/pull/575#discussion_r3217882826


##########
include/pulsar/Authentication.h:
##########
@@ -530,7 +541,14 @@ class PULSAR_PUBLIC AuthOauth2 : public Authentication {
     /**
      * Create an AuthOauth2 with a ParamMap
      *
-     * The required parameter keys are “issuer_url”, “private_key”, and “audience”
+     * For `tokenEndpointAuthMethod = "client_secret_post"` (default), the required parameter
+     * keys are “issuer_url”, “private_key”, and “audience”.
+     * Optional keys: `scope`, `tls_cert_file`, `tls_key_file`.

Review Comment:
   Although the RFC defines `audience` as an optional parameter, it remains required for `client_secret_post` to avoid a breaking change.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@pulsar.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [improve][client] Implement tls_client_auth for AuthOauth2 [pulsar-client-cpp]

Posted by "BewareMyPower (via GitHub)" <gi...@apache.org>.
BewareMyPower merged PR #575:
URL: https://github.com/apache/pulsar-client-cpp/pull/575


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@pulsar.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [improve][client] Implement tls_client_auth for AuthOauth2 [pulsar-client-cpp]

Posted by "izumo27 (via GitHub)" <gi...@apache.org>.
izumo27 commented on code in PR #575:
URL: https://github.com/apache/pulsar-client-cpp/pull/575#discussion_r3240661338


##########
include/pulsar/Authentication.h:
##########
@@ -515,11 +515,22 @@ typedef std::shared_ptr<CachedToken> CachedTokenPtr;
  * Passed in parameter would be like:
  * ```
  *   "type": "client_credentials",
+ *   "tokenEndpointAuthMethod": "client_secret_post",
  *   "issuer_url": "https://accounts.google.com",
  *   "client_id": "d9ZyX97q1ef8Cr81WHVC4hFQ64vSlDK3",
  *   "client_secret": "on1uJ...k6F6R",

Review Comment:
   Both client_secret and private_key are supported.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@pulsar.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [improve][client] Implement tls_client_auth for AuthOauth2 [pulsar-client-cpp]

Posted by "izumo27 (via GitHub)" <gi...@apache.org>.
izumo27 commented on PR #575:
URL: https://github.com/apache/pulsar-client-cpp/pull/575#issuecomment-4449830943

   Resolved copilot review.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@pulsar.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org