curl

August 27, 2026 · View on GitHub

Smallest possible end-to-end exercise: run ch-jwt-verify locally with your real IdP's config, hit /verify with curl carrying a JWT you have already minted. Same wire contract ClickHouse uses (Authorization: Basic base64(user:JWT)), so any rejection you see is the exact rejection ClickHouse would report.

Different shape from the other consumer examples: this one is sidecar-only, BYO JWT. The other examples (../superset/docker/, ../python/docker/, …) bring up a full stack with Dex as the IdP, so you can drive the wire end-to-end without an external IdP at all. Use those when iterating on the consumer side; use this one when bringing the sidecar up against a brand-new external IdP for the first time.

Run

# 1. Fill in oauth.issuer + oauth.audience in config.yaml.
$EDITOR examples/curl/config.yaml

# 2. Start the sidecar locally (from the repo root):
go run ./cmd/ch-jwt-verify -c examples/curl/config.yaml &

# 3. Mint a JWT from your IdP (Auth0, Google, etc.) for the configured audience,
#    then hit /verify with it:
examples/curl/verify.sh alice@example.com "eyJhbGciOi…"
# or via env:
CH_JWT_USER=alice@example.com CH_JWT_TOKEN="eyJhbGciOi…" \
  examples/curl/verify.sh

Expected on success:

HTTP/1.1 200 OK
Content-Type: application/json
{"settings":null,"email":"alice@example.com"}

On rejection the sidecar returns HTTP/1.1 403 Forbidden with a single fixed body, authentication failed — deliberately the same text regardless of why the request was rejected (bad signature, wrong issuer/audience, expired token, username mismatch, reserved username, or email/domain policy). A caller can never distinguish which check failed from the response alone; that's a security property (see cmd/ch-jwt-verify/verify.go), not a missing detail. Run the sidecar with --log-level debug to see the real reason in the server-side log line — the body never echoes the token, and the debug log never echoes it either.

Common failures

SymptomLikely cause
403 authentication failedAny rejection — bad signature, wrong issuer, wrong audience, expired/missing exp, Basic-auth user not matching the configured identity.username_claim, a reserved (denied_usernames) user, or identity.require_email_verified/domain-allowlist policy. Check the sidecar's debug log for the specific cause.
503 on /readyzJWKS fetch failing — see sidecar logs; usually a misconfigured issuer or a network policy blocking egress.