OAuth helpers for ClickHouse-fronted deployments. The first (and currently
only) helper is ch-jwt-verify: a tiny HTTP server that ClickHouse calls from
its <http_authentication> handler to validate JWT bearers.
Native OAuth / JWT authentication isn't in stock OSS ClickHouse. Today you get it in two places only:
- ClickHouse Cloud (commercial), where SSO + JWT login is wired into the managed control plane.
- Altinity Antalya, Altinity's ClickHouse fork, which accepts
Authorization: Bearer <jwt>directly on the HTTP interface and validates it against a JWKS the server is configured to trust.
If you're running OSS ClickHouse outside Cloud, neither is available. The
workaround this repo implements is to lean on ClickHouse's built-in
<http_authentication_servers> mechanism: define an external
HTTP authenticator, point ClickHouse at it, and let that service do the JWT
validation. ClickHouse forwards the Authorization header to the helper on
every authenticated request and trusts a 200 OK response.
ch-jwt-verify is that helper. The catch is that ClickHouse's HTTP auth
backend only understands HTTP Basic — there's no Bearer path on OSS — so
upstream consumers (Superset, Grafana, MCP servers, your own scripts) have to
repack the user's JWT as the Basic-auth password (base64(email:jwt)) before
calling ClickHouse. The helper then pulls the JWT back out of the password
field, verifies it (signature, iss/aud/exp, identity policy), and
either returns 200 or rejects. See the wire diagram below for the full path.
If you can run Antalya, do — the entire repacking trick + this sidecar go away. This repo is the bridge for everyone still on OSS.
A single-binary sidecar designed to run colocated with a ClickHouse pod (StatefulSet or Deployment). Wire contract:
client ── Authorization: Bearer <JWT> ──> upstream (MCP / Superset / Grafana / …)
rewrites to
client ── Authorization: Basic base64(email:JWT) ──> ClickHouse :8123
<http_authentication_servers>
│
▼
ch-jwt-verify GET/POST /verify
│ validates signature, iss, aud,
│ exp/nbf/iat, required scopes,
│ identity policy (verified-email,
│ allowed domains), user↔email match
▼
200 + optional CH session settings → CH accepts query
non-200 → CH returns 516 WRONG_PASSWORD
The sidecar is the only cryptographic gate on the path: the upstream is a pure forwarder. Per-user provisioning on the CH side:
CREATE USER "alice@example.com"
IDENTIFIED WITH http SERVER 'ch_jwt_verify' SCHEME 'BASIC'
DEFAULT ROLE mcp_reader;(The grammar token is http, not http_authenticator — the latter is
rejected with SYNTAX_ERROR.)
git clone https://github.com/altinity/altinity-oauth-helper
cd altinity-oauth-helper
$EDITOR examples/curl/config.yaml # fill in issuer + audience
go run ./cmd/ch-jwt-verify -c examples/curl/config.yaml &
examples/curl/verify.sh alice@example.com "$JWT"
# → HTTP/1.1 200 OK
# {"settings":null,"email":"alice@example.com"}See examples/curl/README.md
for the failure-mode cheatsheet.
The Helm chart in helm/ch-jwt-verify/ renders two
ConfigMaps (sidecar YAML, CH <http_authentication_servers> XML) and a
reusable container fragment you splice into your CH pod spec. It does not
render a Deployment/StatefulSet — the sidecar must share a pod with
ClickHouse so the loopback trust model holds.
See helm/ch-jwt-verify/README.md for the
wiring (including the clickhouse-operator default emptyDir quirk).
For worked end-to-end examples (Superset, Grafana, …), see
examples/ — the examples/README.md
matrix tracks which consumer × deploy-style combinations are working.
scripts/build-image.sh feature-strict-iss
# → ghcr.io/altinity/ch-jwt-verify:feature-strict-iss-<short-sha>
# (multi-arch manifest + per-arch -amd64 / -arm64 tags)The script cross-compiles statically-linked binaries for amd64+arm64, builds
with legacy DOCKER_BUILDKIT=0, pushes per-arch, and assembles the multi-arch
manifest. Image name stays at ghcr.io/altinity/ch-jwt-verify so existing
Helm values continue to work.
cmd/ch-jwt-verify/ # the sidecar binary (main, config, settings, verify)
helm/ch-jwt-verify/ # Helm chart (ConfigMaps + container fragment, no Deployment)
scripts/build-image.sh # multi-arch image build & push
examples/ # _platform shared compose base, plus curl / superset /
# grafana consumer overlays (see examples/README.md)
Dockerfile # consumed by scripts/build-image.sh
JWKS fetching, JWT validation, and the shared identity-policy helpers live
in github.com/altinity/go-mcp-oauth-sdk;
this repo consumes that module via go.mod.
Apache 2.0 — see LICENSE.