Skip to content

Implement native login UI (replace Zitadel hosted v2 login) #274

Description

@JohnRDOrazio

Goal

Implement the login / register / sign-out flow natively inside ontokit-web, calling Zitadel's HTTP APIs directly from our own Next.js routes & components — instead of redirecting the user to Zitadel's hosted v2 login UI at auth.catholicdigitalcommons.org/ui/v2/login/*.

End-state: a user clicking "Sign in" on the ontokit-web origin stays on the same origin from start to finish. The browser never leaves the consumer domain; Zitadel becomes an authorization backend reached over HTTPS APIs, not a redirected-to identity UI.

Why

  1. Console-noise on every login. The hosted v2 login UI is a Next.js client that optimistically tries to client-side-transition into the OIDC redirect_uri after the 302. Because ontokit-web lives on its own origin and auth lives on auth.catholicdigitalcommons.org, that prefetch is structurally cross-origin and always fails — first by CSP, now (post umbrella CSP-widening) by CORS. Login still works (the browser falls back to a regular navigation), but every login attempt produces a wall of Failed to fetch RSC payload … Falling back to browser navigation errors that look broken to anyone watching DevTools.
  2. Original umbrella intent. Each property was always supposed to own its own login UI (it's why setup-zitadel.sh provisions a login-client machine-user PAT alongside the OIDC client registration). The hosted UI was a stopgap.
  3. UX consistency. Sign-in lives in the same visual language as the rest of ontokit-web.
  4. Drops the hosted login as a deployable. Once every umbrella property has its own login, the zitadel-login container can be removed from cdcf-infra.

Approach

Since ontokit-web is Next.js (per repo description), the approach mirrors cdcf-website almost exactly:

  • Keep whichever session-cookie layer is in place today (Auth.js v5 if present); switch from the Zitadel OIDC provider (which assumes the hosted UI) to a Credentials provider that finalizes against Zitadel after our own pages complete the Session API flow.
  • New pages: /auth/login, /auth/register, /auth/verify-email, /auth/reset-password, /auth/mfa-setup.
  • Existing /api/auth/callback/zitadel callback handler stays — the redirect_uri shape is unchanged.

Zitadel APIs to wire

  • Session APIPOST /v2/sessions, PATCH /v2/sessions/{id}, DELETE /v2/sessions/{id}.
  • User APIPOST /v2/users/human for registration; GET /v2/users for lookup.
  • OIDC servicePOST /v2/oidc/auth_requests/{authRequestId}/sessions to bind the session to the in-flight OIDC auth request.
  • Auth methods — password, passkey/WebAuthN, TOTP.
  • Self-service — password reset, email verification.

Authentication to these APIs uses the login-client machine-user PAT (the same one zitadel-login consumes today).

Pre-existing parallel work

  • cdcf-website tracking issue: CatholicOS/cdcf-website#189 — same goal, same stack. Discovery from Phase 1 there directly applies here. Coordinate to avoid duplicating the API-call mapping work.
  • cdcf-infra: the hosted login UI's CSP was widened in cdcf-infra PR Upgrade lucide-react from 0.468 to 0.577 #15 to silence the immediate noise across all umbrella properties. That workaround can be retired once every property finishes native-login migration.

Out of scope

  • Replacing auth.catholicdigitalcommons.org itself. Zitadel keeps running there for OIDC discovery, JWKS, /oidc/v1/userinfo, /oidc/v1/end_session, and /management/v1/*.
  • Cross-property SSO.

Risks

  • Auth UX bugs are visible immediately + have security implications. Roll out on staging first; gate on a feature flag so reverts don't need a re-deploy.
  • Passkey/WebAuthN is non-trivial — phase it after password + email verification.

Suggested phases

  1. Discovery & spike — record the exact API calls the v2 login UI makes for password sign-in. Coordinate with cdcf-website#189.
  2. Read-only login/auth/login with email + password against the Session API, behind a feature flag on staging.
  3. Register + email verification.
  4. Passkey + MFA.
  5. Cut-over — flip the flag on prod, observe, remove the hosted-UI dependency.

Default branch

This repo defaults to dev. Branch off and PR against dev.

Metadata

Metadata

Assignees

No one assigned

    Labels

    UXUser experience improvementsenhancementNew feature or requestsecuritySecurity hardening or vulnerability fixes

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions