mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
# Which Problems Are Solved When an OIDC authorize request carries a `login_hint` that resolves via domain discovery to an organization configured to auto-redirect to a single external IdP, Login V2's server-side flow initiation returns a malformed `Location` header: the login base path is concatenated with the absolute IdP authorize URL without a separator (e.g. `https://<instance>/ui/v2/loginhttps://login.microsoftonline.com/...`). The flow ends in `{"code":5,"message":"Not Found"}` and the IdP is never contacted. The same IdP works when the user submits the login name interactively, because the client-side handler distinguishes external URLs; only the server-side `login_hint` fast-path introduced in #12431 is affected. Additionally, the same fast-path only handled the `redirect` response shape from `sendLoginname`. When the discovered organization's IdP is SAML with POST binding, `sendLoginname` returns `samlData` (form fields instead of a URL), which was silently ignored — the user fell back to the prefilled `/loginname` screen instead of being signed in silently, unlike the client-side flow, which auto-submits the SAML AuthnRequest form. # How the Problems Are Solved `resolveLoginHint` passed every redirect returned by `sendLoginname` through `constructUrl`, which unconditionally prepends `NEXT_PUBLIC_BASE_PATH` and assumes a relative path. It now checks `isExternalUrl` first: absolute URLs are validated with `isSafeRedirectUri` (blocking `javascript:`/`data:`/etc., falling back to `/loginname` if unsafe) and redirected as-is, while relative paths keep being resolved against the base path — matching the handling already used in the idp-scope branch of `handleOIDCFlowInitiation` and in the client-side `handleServerActionResponse`. `resolveLoginHint` also handles the `samlData` response shape now: after validating the target URL with `isSafeRedirectUri`, it responds with the same auto-submit HTML form used by the existing SAML flows, so a `login_hint` resolving to a POST-binding SAML IdP completes silently as well. Since this form was previously duplicated inline three times in `flow-initiation.ts`, it is extracted into a shared `buildAutoSubmitFormResponse` helper used by all call sites. Regression tests cover the absolute-URL redirect (fails on the previous code with the exact malformed URL), the SAML POST auto-submit response, and the unsafe-scheme fallbacks for both shapes.
3.0 KiB
3.0 KiB
ZITADEL Login App Guide for AI Agents
Context
The Login App (apps/login) provides the user interface for authentication flows (Login, Register, MFA, etc.). It is built with Next.js and React.
Key Technology
- Framework: Next.js (React).
- Styling: TailwindCSS, configured via
apps/login/tailwind.config.mjs. - Data Fetching: Primarily server-side interaction with ZITADEL APIs via
@zitadel/clientor direct gRPC calls where applicable. - Language: TypeScript.
Architecture & Conventions
- Routing: Uses the Next.js App Router (routes are defined under
src/app/). - Composability: Components should be small and reusable.
- State: Critical authentication state is often managed via URL parameters (Auth Requests) and cookies/sessions.
- Scope Rule: For shared API typings and client behavior, also read
packages/AGENTS.mdandproto/AGENTS.md.
Base Path & Redirects (critical — read before touching any redirect logic)
- The login app is served under a non-root base path in production. ZITADEL Cloud and the official container image are built with
NEXT_PUBLIC_BASE_PATH=/ui/v2/login(seeapps/login/.env; the value is inlined at build time by Next.js). Never assume the app lives at the domain root. constructUrl()(src/lib/service-url.ts) prepends the base path and must ONLY ever receive root-relative paths (/loginname,/password?...). Passing an absolute URL glues the base path onto it and produces a broken redirect likehttps://<host>/ui/v2/loginhttps://login.microsoftonline.com/....- Some redirect targets ARE absolute URLs: IdP authorize URLs returned by
startIdentityProviderFlow, OIDC callback URLs fromcreateCallback, and SAML endpoints fromcreateResponse. When handling a redirect value that could be absolute, branch onisExternalUrl()first, validate absolute URLs withisSafeRedirectUri(), and pass them toNextResponse.redirect(server) /window.location.href(client) untouched. Canonical pattern:resolveLoginHintinsrc/lib/server/flow-initiation.tsandhandleServerActionResponseinsrc/lib/client-utils.ts. - An empty base path masks this whole bug class: with
NEXT_PUBLIC_BASE_PATHunset,new URL("" + absoluteUrl, origin)still parses the absolute URL correctly, so local setups without the base path won't reproduce base-path bugs. Always test redirect changes withNEXT_PUBLIC_BASE_PATH=/ui/v2/loginset, and verify the rawLocationheader (e.g.curl -I), not just browser behavior.
Verified Nx Targets
- Dev Server:
pnpm nx run @zitadel/login:dev - Build:
pnpm nx run @zitadel/login:build - Lint:
pnpm nx run @zitadel/login:lint - Test (all):
pnpm nx run @zitadel/login:test - Test (unit):
pnpm nx run @zitadel/login:test-unit - Test (integration):
pnpm nx run @zitadel/login:test-integration - Pack (Docker):
pnpm nx run @zitadel/login:pack— builds a local Docker imagezitadel/zitadel-login:local. Requires Docker daemon.