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.
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_staff—forum.is_staff(sub)must return true. The gate mints a short-livedservice_roleJWT fromSUPABASE_JWT_SECRETto call PostgREST, so the stale stored service-key is never used.
Configuration
Section titled “Configuration”| Env | Default | Purpose |
|---|---|---|
GATE_LISTEN | 0.0.0.0:5678 | bind address |
GATE_UPSTREAM | http://127.0.0.1:5679 | upstream base URL |
GATE_UPSTREAM_PREFIX | — | path prefix prepended to every upstream request (Grafana sub-path) |
GATE_UPSTREAM_CA_CERT_PATH | — | CA bundle to trust an HTTPS upstream (ArgoCD TLS) |
GATE_AUTHZ | is_staff | is_staff or jwt-only |
GATE_STAFF_SCHEMA | forum | Postgres schema holding is_staff |
GATE_UPSTREAM_BASIC | — | Basic <b64> injected downstream |
GATE_UPSTREAM_BEARER | — | Authorization: Bearer <token> injected downstream (ArgoCD apiKey) |
GATE_FORWARD_USER_HEADER / GATE_FORWARD_USER_VALUE | — | trusted identity header injected downstream (Grafana auth-proxy) |
GATE_LOGIN_REDIRECT | — | 302 target for unauthed browser navigations |
GATE_COOKIE_DOMAIN | — | domain scope for the minted session cookie (e.g. .kbve.com) |
GATE_EXTERNAL_BASE | — | public origin for redirect_to (e.g. https://supabase.kbve.com); required behind a proxy that rewrites Host or drops X-Forwarded-Proto |
SUPABASE_JWT_SECRET | — | required |
SUPABASE_URL | — | required 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:
- Unauthed navigation to a gated host →
302toGATE_LOGIN_REDIRECT?redirect_to=<original URL>. - The login page, once a session exists, returns the browser to
redirect_to?access_token=<jwt>. - The gate validates the token, sets a
kbve_gatecookie scoped toGATE_COOKIE_DOMAIN, and302s 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+HTTPRouteand proxies a separate in-clusterService. Used for Helm- or operator-managed upstreams we don’t own (Grafana, Longhorn, ArgoCD).
n8n (sidecar)
Section titled “n8n (sidecar)”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 (standalone)
Section titled “Grafana (standalone)”grafana.kbve.com → standalone gate in monitoring → monitoring-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 (standalone)
Section titled “Longhorn (standalone)”longhorn.kbve.com → standalone gate in longhorn-system →
longhorn-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.
ArgoCD (standalone)
Section titled “ArgoCD (standalone)”argo.kbve.com → standalone gate in argocd → argocd-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 (standalone)
Section titled “Forgejo (standalone)”forgejo.kbve.com → standalone gate in forgejo → forgejo-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.