Skip to content
docker · networking · auth

Supabase JWT authreverse-proxy

A thin authentication reverse-proxy built on the kbve crate's feature-gated gate module. It runs as a sidecar in front of an internal service so the upstream binds localhost and only the gate is exposed — fronting n8n, Grafana, Longhorn, ArgoCD, Forgejo, and Supabase Studio.

Validate then proxy

The gate accepts a Supabase JWT from a bearer header, an sb-access-token/kbve_gate cookie, or a query param, then applies an authz policy before forwarding to the localhost-bound upstream.

  • jwt-only — any valid Supabase JWT passes.
  • is_staff — forum.is_staff(sub) must return true.
kbve-gateApp
kbve gate moduleBuilt on
kbve/kbve-gateImage
Supabase JWTAuth

What it gives you

Features

JWT + authz policy

Validates a Supabase JWT, then enforces jwt-only or is_staff via a short-lived service_role token.

Login bounce

302s unauthed cross-subdomain navigations to the login page and sets a scoped kbve_gate cookie on return.

Sidecar or standalone

Shares a pod with an editable upstream, or runs as its own Deployment + Service + HTTPRoute for ones we don't own.

Downstream identity injection

Injects basic-auth, bearer tokens, or a trusted user header so gated tools trust the gate-authed staff user.

Fronts the toolchain

Guards n8n, Grafana, Longhorn, ArgoCD, Forgejo, and Supabase Studio behind one auth model.

Access

Auth & configuration

The gate accepts a Supabase JWT from an Authorization: Bearer header, an sb-access-token / kbve_gate cookie, or a ?access_token= query param. After the JWT validates it applies an authz policy:

  • jwt-only — any valid Supabase JWT passes.
  • is_staffforum.is_staff(sub) must return true. The gate mints a short-lived service_role JWT from SUPABASE_JWT_SECRET to call PostgREST, so the stale stored service-key is never used.
EnvDefaultPurpose
GATE_LISTEN0.0.0.0:5678bind address
GATE_UPSTREAMhttp://127.0.0.1:5679upstream base URL
GATE_UPSTREAM_PREFIXpath prefix prepended to every upstream request (Grafana sub-path)
GATE_UPSTREAM_CA_CERT_PATHCA bundle to trust an HTTPS upstream (ArgoCD TLS)
GATE_AUTHZis_staffis_staff or jwt-only
GATE_STAFF_SCHEMAforumPostgres schema holding is_staff
GATE_UPSTREAM_BASICBasic <b64> injected downstream
GATE_UPSTREAM_BEARERAuthorization: Bearer <token> injected downstream (ArgoCD apiKey)
GATE_FORWARD_USER_HEADER / GATE_FORWARD_USER_VALUEtrusted identity header injected downstream (Grafana auth-proxy)
GATE_LOGIN_REDIRECT302 target for unauthed browser navigations
GATE_COOKIE_DOMAINdomain scope for the minted session cookie (e.g. .kbve.com)
GATE_EXTERNAL_BASEpublic origin for redirect_to (e.g. https://supabase.kbve.com); required behind a proxy that rewrites Host or drops X-Forwarded-Proto
SUPABASE_JWT_SECRETrequired
SUPABASE_URLrequired for is_staff (direct PostgREST)

Cross-subdomain

Login bounce

The KBVE session lives in the browser (IndexedDB), so a cross-subdomain navigation carries no token. The gate completes auth with a bounce:

  1. Unauthed navigation to a gated host → 302 to GATE_LOGIN_REDIRECT?redirect_to=<original URL>.
  2. The login page, once a session exists, returns the browser to redirect_to?access_token=<jwt>.
  3. The gate validates the token, sets a kbve_gate cookie scoped to GATE_COOKIE_DOMAIN, and 302s to a clean URL. Subsequent requests carry the cookie.

The login page only honours redirect_to targets on *.kbve.com over https, so the token can never be handed to a foreign origin.

redirect_to carries the full path and query, so a deep link (/project/default/sql?schema=public) survives the round trip. Any access_token already in the query is dropped rather than echoed back.

redirect_to is otherwise derived from Host + X-Forwarded-Proto. A gate behind a second reverse proxy can see neither: Supabase Studio sits behind Kong, which without preserve_host sends Host: studio-gate:5678 and stamps X-Forwarded-Proto: http for its own plaintext listener. The gate then emits http://studio-gate:5678/..., the login page rejects it, and the user lands on the homepage instead of the gated host.

GATE_EXTERNAL_BASE pins the public origin and bypasses both headers. It is set on studio, grafana, argo, longhorn, and windmill so no future proxy change can break the bounce. A bad value fails at boot rather than at login. Forgejo deliberately leaves it unset — it serves forgejo.kbve.com, git.kbve.com, and the DNS-only direct.git.kbve.com from one route, so pinning one origin would bounce every login away from the other two. When the value is unset and the derived target is not https on a public hostname, the gate logs a warning naming this env var.

Consumers

Deployment shapes

Two shapes depending on whether the upstream is in the same pod:

  • Sidecar — the gate shares a pod with the upstream, which binds 127.0.0.1. Used when the workload is ours to edit (n8n).
  • Standalone — the gate is its own Deployment + Service + HTTPRoute and proxies a separate in-cluster Service. Used for Helm- or operator-managed upstreams we don’t own (Grafana, Longhorn, ArgoCD).

The first consumer fronts n8n: n8n moves to 127.0.0.1:5681, the gate owns the Service port 5678, and n8n.kbve.com routes through it so the panel stays staff-only.

grafana.kbve.com → standalone gate in monitoringmonitoring-grafana:80. GATE_UPSTREAM_PREFIX=/dashboard/grafana/proxy matches Grafana’s sub-path root_url. Grafana’s own login is bypassed with its auth-proxy: GATE_FORWARD_USER_HEADER=X-WEBAUTH-USER / GATE_FORWARD_USER_VALUE=kbve-staff, so the gate-authed staff user is trusted downstream.

longhorn.kbve.com → standalone gate in longhorn-systemlonghorn-frontend:80. No prefix (the UI roots at /) and no upstream login — the gate’s is_staff is the only auth in front of the panel.

argo.kbve.com → standalone gate in argocdargocd-server:443 over HTTPS (GATE_UPSTREAM_CA_CERT_PATH trusts the argocd-server-tls CA). ArgoCD keeps its own login, so the gate injects an admin apiKey as GATE_UPSTREAM_BEARER (synced by ESO from the argocd-auth secret) on every request — the SPA then authenticates as that account instead of redirecting to ArgoCD’s login.

forgejo.kbve.com → standalone gate in forgejoforgejo-http:3000. The gate enforces is_staff authz while API and git operations (e.g., /api/, /v2/, /KBVE/) bypass the gate for token-based auth. Forgejo is configured with OAuth/OIDC to use Supabase as the identity provider, enabling automatic account creation and login for authenticated staff users.

Questions

Frequently asked

What is KBVE Gate?

KBVE Gate is a thin authentication reverse-proxy built on the kbve crate's feature-gated gate module. It runs as a sidecar in front of an internal service so the upstream binds localhost and only the gate is exposed, fronting tools like n8n, Grafana, Longhorn, ArgoCD, Forgejo, and Supabase Studio.

How does KBVE Gate authenticate requests?

The gate accepts a Supabase JWT from an Authorization Bearer header, an sb-access-token or kbve_gate cookie, or an access_token query param. After the JWT validates it applies an authz policy — jwt-only lets any valid JWT pass, while is_staff requires forum.is_staff(sub) to return true.

How does KBVE Gate handle cross-subdomain login?

Because the KBVE session lives in the browser's IndexedDB, a cross-subdomain navigation carries no token, so the gate does a login bounce — it 302s unauthed navigations to GATE_LOGIN_REDIRECT, the login page returns with access_token, and the gate validates it and sets a kbve_gate cookie scoped to GATE_COOKIE_DOMAIN.