Skip to content

Central authorization Phase 2: OIDC upstream, groups, template inheritance, TOTP #18

Description

@swimmesberger

Follow-up to the central-authorization Phase 1 work (#17). Phase 1 delivered the control plane — local users, revocable DB sessions, native Watchtower login, forward-auth (verify + cross-domain dance + ES256 JWT/JWKS), per-route access policy, rate limiting, and audit rows. This issue tracks Phase 2: identity federation & groups, plus the concrete follow-ups the Phase 1 review process surfaced.

Design reference: docs/central-auth/design.md §2.8, §9 (Phase 2), §11.

Phase 2 scope

1. Generic OIDC upstream (= Keycloak / Entra / Google)

The login page gains a "Continue with SSO" path: standard OIDC authorization-code flow against a configured provider, users provisioned just-in-time and linked by issuer + sub. Watchtower stays the gate — it still owns per-app access policy; the external IdP only answers "who is this person" (design §2.1). Needs User.ExternalIssuer / ExternalSubject (unique pair), provider config, and the callback wiring.

2. Groups + group-based grants

Model the access-policy subject as principals (users or groups), not just users. RouteAccessGrant currently binds (RouteId, UserId); extend to allow a group principal. Group membership feeds RouteAccessPolicy.IsAuthorizedAsync and the X-Watchtower-Groups convenience header (already reserved in the header contract). Admin UI to manage groups + membership.

3. Template policy inheritance for tenants

Phase-2 StackTemplate tenant instances get routes auto-created; access policy (mode + grants) should be inheritable from the template so per-tenant apps don't each need manual policy. Wants group-per-tenant semantics ("tenant X's users see tenant X's instance").

4. TOTP (MFA)

The Identity-core security-stamp plumbing is already in place (Phase 1), making TOTP a bounded addition. Enrollment + verification flow; passkeys/WebAuthn optional beyond that.

Concrete follow-ups surfaced during Phase 1 review (smaller, independently landable)

  • Audit-viewing surface. Phase 1 writes AuthEvent rows (logins, denials, user + policy changes, break-glass) but ships no UI/query API over them. A read-only audit view is the natural next step.
  • X-Forwarded-For for rate limiting. The login rate limiter partitions on the connection IP (deliberately not the spoofable XFF, per the forwarded-headers stance). Behind the single Caddy proxy this makes the limit instance-global. If per-client limiting behind the proxy is wanted, add XFF processing with a trusted-proxy allowlist (the dynamic Docker subnet makes source-pinning non-trivial — see the Program.cs forwarded-headers note).
  • Cookie-tossing hardening. A sibling subdomain can set __wt_access with a parent Domain. The __Host- cookie prefix would close it but requires unconditional Secure, which conflicts with the published-port recovery path. Revisit if/when that trade-off changes.
  • Auth-host bootstrap UX (design §11). The login page must be reachable before auth fully works (Auth:Host needs DNS + the self-route first). A guided setup flow (create self-route → verify cert → then allow enabling forward-auth elsewhere) would de-risk operator lockout.
  • Elarion "B1" footgun (upstream). The schema exporter resolves a relative --export-schema path against the project dir, not the invocation cwd — Phase 1 worked around it in CI/docs, but the exporter itself refusing a bare relative path (or resolving against cwd) would remove the trap for good.

Not in scope here

Phase 3 items (service tokens, per-tenant IdPs, SCIM) — separate issue when needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions