fix(api): add opt-in OIDC_DEFAULT_TENANT fallback (fail-closed by default) - #4
Conversation
|
Thanks for your PR. Please review the comment #1 (comment) so we can decide how to proceed. |
…s + tests Per review on syzygyhack#1: drop the implicit `?? 'default'` (fail-open) in favor of an explicit opt-in env knob. Unset arrives as undefined and configure() normalizes it to null, preserving the documented fail-closed posture (claimless tokens are rejected). Single-tenant deployments set OIDC_DEFAULT_TENANT=default; multi-tenant deployments leave it unset and map real tenants via a tenant_id claim. - packages/api/src/server.ts: opt-in wiring + env doc. - deploy/README.md, .env.example, docker-compose.yaml, Helm values + configmap: document/surface the knob (fail-closed by default). - oidc-authenticator.test.ts: assert unset stays fail-closed and set opts into the fallback tenant.
|
Thanks for the thorough review — agreed on both points. Pushed a revision ( Fail-open → opt-in. Dropped the implicit Docs + surfaces. Documented the knob in Test. Added On the framing: you're right that a fresh bundle authenticates — the realm now ships the |
|
Verified locally on
Your persisted-realm diagnosis is right, realm import only runs against a fresh Keycloak DB, so environments predating the Nice work. I handled the Trivy check, it's just CVE drift, so unrelated. LGTM! |
Problem
In production (
NODE_ENV=production), every authenticated request fails with401 Authentication required, even with a fully valid bearer token.The root cause is not signature/JWKS verification —
jose.jwtVerifysucceeds with the exact issuer/audience/JWKS (reproduced standalone). It is tenant resolution:OidcAuthenticatordefaults totenantClaim = "tenant_id"anddefaultTenantId = null.packages/api/src/server.tscallsauthenticator.configure({...})with onlyissuer,clientId,jwksUri— it never passesdefaultTenantId.jwtVerify,extractUser()readsclaims["tenant_id"]; a stock Keycloak access token has no such claim →tenantId = null→AuthenticationError("MISSING_TENANT")→ mapped to 401.The repo's own
deploy/docker-compose.yamlKeycloak issues tokens without atenant_idclaim, so the default production deployment cannot authenticate any user.Reproduce
NODE_ENV=production; wireOIDC_ISSUER/OIDC_CLIENT_ID/OIDC_JWKS_URIto the bundled Keycloak.curl /graphql -H "authorization: Bearer <token>" -d '{"query":"{ __typename }"}'→401 UNAUTHENTICATED.Fix
Add an
OIDC_DEFAULT_TENANTfallback (defaultdefault) in theauthenticator.configure({...})call, so tokens without atenant_idclaim resolve to a sane tenant. Multi-tenant deployments can still map real tenants via atenant_idclaim / Keycloak protocol mapper.Verified
After the fix + image rebuild: no token → 401; a valid token → authenticated (tenant
default); the full governed-action pipeline (create / validate / ReBAC deny / audit) works end-to-end.Fixes #1.