Skip to content

feat: Implement OAuth2 authentication for REST catalog#577

Open
lishuxu wants to merge 2 commits intoapache:mainfrom
lishuxu:feature/oauth
Open

feat: Implement OAuth2 authentication for REST catalog#577
lishuxu wants to merge 2 commits intoapache:mainfrom
lishuxu:feature/oauth

Conversation

@lishuxu
Copy link
Contributor

@lishuxu lishuxu commented Feb 26, 2026

Add OAuth2 authentication support for the REST catalog, including:

  • OAuth2AuthManager with static token and client_credentials grant flows

Also includes:

  • Fix BuildHeaders in http_client.cc to use insert_or_assign so request-specific
    headers properly override defaults

TODO:
RefreshToken and ExchangeToken will be supported later

Copy link
Member

@wgtmac wgtmac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a comprehensive code review report generated by gemini-cli against PR #577. The review rigorously compares the C++ implementation with the RFC 6749 (OAuth2) spec and the current Iceberg Java behavior.

Overall, the structure and C++ style are excellent, but there are a few parity and robustness issues that need addressing.

}
const auto& credential = credential_it->second;
auto colon_pos = credential.find(':');
if (colon_pos == std::string::npos) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Parity Bug: Iceberg Java's OAuth2Util.parseCredential allows credentials without a colon (:). If no colon is found, the entire string is treated as the client_secret and client_id becomes null/empty. This is required for some IdP setups that only provide a secret/bearer token. Please update the parsing logic to fall back to client_secret = credential and client_id = "".

const std::string& scope, AuthSession& session) {
std::unordered_map<std::string, std::string> form_data{
{std::string(kGrantType), std::string(kClientCredentials)},
{std::string(kClientId), client_id},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the Java parity fix above (where client_id might be empty), you should only append client_id to the form_data if it is not empty:

if (!client_id.empty()) {
    form_data.emplace(std::string(kClientId), client_id);
}

ICEBERG_ASSIGN_OR_RAISE(response.token_type,
GetJsonValue<std::string>(json, kTokenType));
ICEBERG_ASSIGN_OR_RAISE(response.expires_in,
GetJsonValueOrDefault<int64_t>(json, kExpiresIn, 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Future-proofing / JWT Expiration: If expires_in is missing and the token is a JWT, Iceberg Java decodes the JWT payload to extract the exp claim. While defaulting to 0 here is acceptable since auto-refresh is marked as a TODO, consider adding a comment here as a reminder for the future auto-refresh implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants