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.
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). NeedsUser.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.
RouteAccessGrantcurrently binds(RouteId, UserId); extend to allow a group principal. Group membership feedsRouteAccessPolicy.IsAuthorizedAsyncand theX-Watchtower-Groupsconvenience header (already reserved in the header contract). Admin UI to manage groups + membership.3. Template policy inheritance for tenants
Phase-2
StackTemplatetenant 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)
AuthEventrows (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-Forfor 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).__wt_accesswith a parentDomain. The__Host-cookie prefix would close it but requires unconditionalSecure, which conflicts with the published-port recovery path. Revisit if/when that trade-off changes.Auth:Hostneeds 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.--export-schemapath 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.