Skip to content

feat(web): add optional platform OIDC authentication - #332

Draft
robspan wants to merge 2 commits into
berntpopp:mainfrom
robspan:web-optional-oidc-auth
Draft

feat(web): add optional platform OIDC authentication#332
robspan wants to merge 2 commits into
berntpopp:mainfrom
robspan:web-optional-oidc-auth

Conversation

@robspan

@robspan robspan commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add opt-in platform OIDC authentication via VARLENS_AUTH_MODE=platform; unset or local mode retains the existing local login
  • implement authorization code with PKCE, RS256/JWKS validation, nonce/state handling, MFA acr/amr and freshness enforcement, and entitlement revalidation
  • add an operator-only CLI to bind one OIDC subject to a VarLens instance and a PostgreSQL invariant preventing a second subject
  • document the web environment configuration and include the CLI in web and container builds

Why

Hosted web deployments need to delegate sign-in to an external identity provider such as Keycloak while keeping VarLens itself single-instance and unaware of platform tenancy.

Compatibility and scope

  • desktop code paths do not read these settings and remain unchanged
  • local username/password authentication remains the default
  • no tenant routing, database provisioning, or platform lifecycle logic is added
  • each VarLens instance still uses exactly one VARLENS_PG_URL

Validation

  • make ci
  • make web-ci against a fresh PostgreSQL database
  • 65 focused platform identity tests
  • platform-mode buildApp integration test
  • built operator CLI load test
  • make agent-check

robspan and others added 2 commits August 7, 2026 10:46
Fastify 5 defaults requestIdHeader to false, so the server already ignored
client-supplied request IDs. But that was inherited behaviour, not a stated
one: the option has changed default across Fastify majors, and a truthy value
resolves to the `request-id` header, letting a client pick its own request ID
and forge or collide log correlation. Declare it explicitly so a future
default flip cannot silently re-enable header trust.

The existing regression test could not have caught this. It spoofed only
`x-request-id`, which Fastify never consults, so it passed identically with
header trust on or off. Add the `request-id` case that actually exercises the
boundary — verified to fail when requestIdHeader is flipped to true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017BwhzZvevbVajuq1nx2FnQ
@berntpopp
berntpopp force-pushed the web-optional-oidc-auth branch from ee58838 to 234c25a Compare August 7, 2026 08:49
@berntpopp

Copy link
Copy Markdown
Owner

Rebased onto main (1fd7b455, v0.70.6) now that #340 and #331 have landed. Two conflicts, both resolved:

  • Makefilefix(web): query cases through PostgreSQL views #340 and this PR each appended a VARLENS_RUN_POSTGRES_E2E=1 line to web-gate-postgres. Resolved as a union: one invocation running all three files. Verified they pass together.
  • tests/main/storage/postgres-migrations-idempotent.test.ts — took this PR's ['0014', '0015', '0016'], since 0016_platform_identity is the migration this branch adds.

src/web/server.ts and src/web/server/routes/auth.ts auto-merged cleanly against #331's metrics changes.

Added commit: fix(web): pin requestIdHeader so client request IDs stay untrusted

Follow-up to a finding from the #331 review, folded in here since this branch is the one in flight.

requestIdHeader is now stated explicitly rather than inherited. Fastify 5 already defaults it to false, so behaviour is unchanged today — but the default has moved across Fastify majors, and a truthy value resolves to the request-id header, which would let a client pick its own request ID and forge or collide log correlation.

The existing regression test could not have caught that. It spoofed only x-request-id, which Fastify never consults, so it passed identically with header trust on or off:

IF requestIdHeader:true had been set:
   spoof via 'x-request-id' -> 63ef607a-…     (ignored)
   spoof via 'request-id'   -> SPOOFED-VALUE  <-- trusted

Added the request-id case that actually exercises the boundary, and verified it now fails when requestIdHeader is flipped to true — so it guards the thing its name claims.

Verification on the rebased branch

  • make typecheck — no errors
  • VARLENS_WEB=1 make test — 4827 passed
  • Postgres integration gate, fresh DB — 17 passed / 1 skipped
  • The merged web-gate-postgres e2e trio — 3 files, 4 passed / 1 skipped

Still draft, and still blocked on the substantive decisions rather than the code — #330's entitlement contract (this PR currently assumes GET {entitlementsUrl}/{subject}{active|allowed, status, role}) and the sub vs (iss, sub) identity-key question from #295.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants