Repository navigation
feat(auth): JWT support for the HTTP client and Cloud JWT tests - #649
polyglotAI-bot wants to merge 2 commits into
Conversation
- Add the BearerToken connection string key. ClickHouseConnection.ConnectionString and ClickHouseDataSource.ConnectionString keep a token set in the settings. - Mask the token in ClickHouseClientSettings.ToString and in the db.connection_string OpenTelemetry tag. - Enable BearerAuthenticationTests again and check that a JWT request runs as the JWT::<sub>::<hash> ephemeral user (settings, connection string, per query). - Cloud job: sign a new JWT for each run from the JWT_PKEY secret (.github/scripts/generate_jwt.py), as clickhouse-java#3099 does. The setup is in .github/scripts/README.md. - Document JWT authentication in docs/http.mdx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The required public JWKS is absent and the Cloud JWT tests have not run successfully end to end.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Adds complete JWT bearer-token support to the HTTP client, including connection strings, redaction, documentation, and Cloud testing infrastructure.
Changes:
- Adds
BearerTokenconnection-string mapping and secret redaction. - Expands unit and Cloud JWT authentication coverage.
- Adds short-lived JWT generation and pinned CI dependencies.
| File | Description |
|---|---|
docs/http.mdx |
Documents JWT authentication. |
ClickHouse.Driver/PublicAPI/PublicAPI.Unshipped.txt |
Records the new public property. |
ClickHouse.Driver/ClickHouseClient.cs |
Redacts tokens from telemetry connection strings. |
ClickHouse.Driver/ADO/ClickHouseConnectionStringBuilder.cs |
Adds and maps BearerToken. |
ClickHouse.Driver/ADO/ClickHouseClientSettings.cs |
Parses, preserves, and masks tokens. |
ClickHouse.Driver.Tests/ClickHouseClientTests.cs |
Tests telemetry redaction. |
ClickHouse.Driver.Tests/ADO/DataSourceTests.cs |
Tests data-source token preservation. |
ClickHouse.Driver.Tests/ADO/ConnectionTests.cs |
Tests reapplied connection authentication. |
ClickHouse.Driver.Tests/ADO/ConnectionStringBuilderTests.cs |
Tests token round trips. |
ClickHouse.Driver.Tests/ADO/ClickHouseClientSettingsTests.cs |
Tests settings redaction. |
ClickHouse.Driver.Tests/ADO/BearerAuthenticationTests.cs |
Re-enables and expands Cloud JWT tests. |
changelog.d/649-jwt-bearer-auth.features.md |
Adds the release-note fragment. |
.github/workflows/tests.yml |
Includes JWT scripts in workflow paths. |
.github/workflows/tests-cloud.yml |
Generates a token for Cloud tests. |
.github/scripts/requirements.txt |
Locks Python dependencies with hashes. |
.github/scripts/requirements.in |
Declares JWT script dependencies. |
.github/scripts/README.md |
Documents Cloud JWT setup. |
.github/scripts/generate_jwt.py |
Signs short-lived test JWTs. |
.github/scripts/generate_jwks.py |
Generates the public JWKS. |
.github/scripts/.gitignore |
Excludes local key artifacts. |
.github/dependabot.yml |
Enables Python dependency updates. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| | Part | Where | Secret? | | ||
| |---|---|---| | ||
| | RSA private key (PEM) | repository secret `JWT_PKEY` | yes | | ||
| | Public key set | `.github/jwks.json` in this repository (step 2 adds it) | no | |
There was a problem hiding this comment.
This is intentional. jwks.json must come from the same private key that becomes the JWT_PKEY secret. That key must stay with a maintainer, so the agent that opened this PR did not make it. Step 2 of "Cloud setup necessary for testing" in the PR description tells how to make jwks.json and commit it to this branch, before the Cloud provider and the secret are set. The README table says "step 2 adds it".
Until the setup is done, the Cloud job skips the JWT tests and shows a warning. That is the result in this run.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |

Summary
Completes JWT (bearer token) authentication in the HTTP client, and makes the Cloud JWT tests run again with a new token for each run. The test setup follows ClickHouse/clickhouse-java#3099.
The client could already send
Authorization: Bearer(ClickHouseClientSettings.BearerToken,QueryOptions.BearerToken,ClickHouseCommand.BearerToken). These parts were missing:ClickHouseConnection.ConnectionStringandClickHouseDataSource.ConnectionStringdropped a token that was set in the settings, so a connection made again from that string used Basic authentication.BearerAuthenticationTestswere[Ignore]d (Temporarily disable BearerAuthenticationTests #359). The Cloud job used a static JWT secret, and that token expires.docs/http.mdxdid not describe JWT authentication.Changes
Client
ClickHouseConnectionStringBuilder.BearerToken: newBearerTokenconnection string key. Both conversions now map it (FromSettingsandClickHouseClientSettings.FromConnectionStringBuilder).PublicAPI.Unshipped.txtis updated.****inClickHouseClientSettings.ToString()and in thedb.connection_stringOpenTelemetry tag (RedactedConnectionString). Without this mask, the new mapping would put the token in that tag in clear text.CI
.github/scripts/generate_jwt.pysigns an RS256 JWT (iss=mydomain.com,aud=ci-test-service,sub=ci-test, valid for 30 minutes) with the private key inJWT_PKEY. These are the claim values that clickhouse-java uses..github/scripts/generate_jwks.pywrites the publicjwks.jsonfor that key. It is the script from the JWT setup guide.tests-cloud.yml:Sign JWTstep masks the token and gives it to theTeststep asCLICKHOUSE_CLOUD_JWT. Only this step getsJWT_PKEY; thedotnet testprocess never gets the private key.INTEGRATIONS_TEAM_TESTS_CLOUD_JWT_DESERT_VM_43is removed.requirements.in→requirements.txt, installed with--require-hashes), because they install in a job that has the Cloud secrets. Dependabot gets apipentry for them.tests.yml:.github/scripts/**is added to the path filters, so that a change to only the scripts starts the Cloud job.Docs
docs/http.mdx: a new "JWT authentication" section, and aBearerTokenrow in the connection settings table.changelog.d/649-jwt-bearer-auth.features.md.Test
BearerAuthenticationTests, skipped when there is noCLICKHOUSE_CLOUD_JWT): the[Ignore]is removed. New tests check thatSELECT currentUser()matches^JWT::.+::.+$, which proves that the server authenticated the request with the JWT, for:ClickHouseClientwithClickHouseClientSettings.BearerToken;ClickHouseConnectionwith theBearerTokenconnection string key;QueryOptions.BearerToken. Only that query runs as the JWT user; the next query runs as the Basic user again.BearerTokenkey round-trips through the connection string, the settings, and back. The cases are a JWT and an opaque token with;,=and".ClickHouseConnection.ConnectionStringsends the token (header checked withTrackingHandler).ClickHouseDataSource.ConnectionStringcontains the token.ToString()andRedactedConnectionStringmask the token.BearerToken = builder.BearerTokenmapping, and the round-trip and connection string tests failed. With the mapping, they pass.--config .github/zizmor.yml) reports no new findings at any severity.kidvalues match, and the JWKS has no private key parameters.Important
The Cloud JWT tests did not run in this PR, because the
JWT_PKEYsecret does not exist yet. The Cloud job only shows the "JWT tests skipped" warning. Please do the setup below, then run the Cloud job again before merge.Cloud setup necessary for testing
These steps are also in
.github/scripts/README.md. Do them outside of the repository directory, so that the private key cannot be committed. Add the secret last: when the secret is set, the job uses it, and the JWT tests fail until the provider accepts the tokens.jwks.json, then commit and push it to this branch (polyglot/jwt-bearer-auth). It contains only the public key.CLICKHOUSE_CLOUD_HOSTsecret. Go to Settings > Security > JWT authentication > Set up JWT providers, and add a provider:clickhouse-cs-ci(you cannot change it later)mydomain.comci-test-servicehttps://raw.githubusercontent.com/ClickHouse/clickhouse-cs/polyglot/jwt-bearer-auth/.github/jwks.json. After the merge, change the URL to.../clickhouse-cs/main/.github/jwks.json.JWT::ci-test::<hash>.gh secret set JWT_PKEY --repo ClickHouse/clickhouse-cs < pkcs8_key.pemBearerAuthenticationTestspass. Then deletepkcs8_key.pem, or keep it only in a secret store.Notes:
JWT_PKEY, and skip steps 2 and 3. The claim values are the same.JWT_PKEYas safe asCLICKHOUSE_CLOUD_PASSWORD. A person who has the key can sign a token withclickhouse:grantsorclickhouse:rolesclaims, up to the provider's permission limit (thedefaultuser).SELECT 1andSELECT currentUser().Not in this PR
CredentialsProvider) adds this for both Basic and Bearer credentials, so this PR does not add a second API for it. Until then, a new token can be passed per request withQueryOptions.BearerTokenorClickHouseCommand.BearerToken; the docs say this.QueryOptions.BearerToken = ""falls back to Basic authentication, not to the client token. The cause is the??inAddDefaultHttpHeaders. This behavior existed before this PR, and this PR does not change it.Checklist
🤖 Generated with Claude Code