feat(login): redirect configuration options in multi-domain setups (#11527)

# Which Problems Are Solved

This PR introduces robust redirect configuration options and enhances
security for Server Actions in multi-domain setups.

# How the Problems Are Solved

- Redirect Fallback Logic: Implemented a unified `resolveRedirectUri`
utility that prioritizes redirect targets as follows:
  - Environment override (`DEFAULT_REDIRECT_URI`)
  - Organization settings (`defaultRedirectUri`).
  - Relative fallback (`/signedin?...`).
- Host Reflection: `DEFAULT_REDIRECT_URI` supports absolute URLs or
relative paths via host reflection.
- Server Action Security: Configured `serverActions.allowedOrigins` to
be dynamically manageable via the `SERVER_ACTION_ALLOWED_ORIGINS`
environment variable (comma-separated list). It defaults to an empty
list for maximum security.
- UI Integration: Updated the /signedin page to use the unified redirect
logic, ensuring consistent behavior for the "Continue" button.
- Documentation: Added type definitions and detailed descriptions for
new environment variables in
`next-env-vars.d.ts`

---------

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Max Peintner
2026-02-24 11:02:29 +03:00
committed by GitHub
co-authored by Copilot
parent 87aae1b5fc
commit d27833e1bc
22 changed files with 267 additions and 53 deletions
@@ -16,7 +16,7 @@ import { TextInput } from "./input";
import { PasswordComplexity } from "./password-complexity";
import { Spinner } from "./spinner";
import { Translated } from "./translated";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { AutoSubmitForm } from "./auto-submit-form";
type Inputs =
@@ -7,7 +7,7 @@ import { useRouter } from "next/navigation";
import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
import { Translated } from "./translated";
import { useState } from "react";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { AutoSubmitForm } from "./auto-submit-form";
import { Alert } from "./alert";
+2 -1
View File
@@ -1,6 +1,6 @@
"use client";
import { handleServerActionResponse, completeFlowOrGetUrl } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { updateOrCreateSession } from "@/lib/server/session";
import { create } from "@zitadel/client";
import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
@@ -17,6 +17,7 @@ import { TextInput } from "./input";
import { Spinner } from "./spinner";
import { Translated } from "./translated";
import { AutoSubmitForm } from "./auto-submit-form";
import { completeFlowOrGetUrl } from "@/lib/client";
// either loginName or sessionId must be provided
type Props = {
+1 -1
View File
@@ -9,7 +9,7 @@ import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { Alert } from "./alert";
import { BackButton } from "./back-button";
import { Button, ButtonVariants } from "./button";
+1 -1
View File
@@ -14,7 +14,7 @@ import { Button, ButtonVariants } from "./button";
import { TextInput } from "./input";
import { Spinner } from "./spinner";
import { Translated } from "./translated";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { AutoSubmitForm } from "./auto-submit-form";
type Inputs = {
@@ -1,7 +1,7 @@
"use client";
import { registerUserAndLinkToIDP } from "@/lib/server/register";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { FieldValues, useForm } from "react-hook-form";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { registerUser } from "@/lib/server/register";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
import { LoginSettings, PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { useRouter } from "next/navigation";
+2 -1
View File
@@ -1,7 +1,6 @@
"use client";
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
import { completeFlowOrGetUrl, handleServerActionResponse } from "@/lib/client";
import { addU2F, verifyU2F } from "@/lib/server/u2f";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
@@ -13,6 +12,8 @@ import { Button, ButtonVariants } from "./button";
import { Spinner } from "./spinner";
import { Translated } from "./translated";
import { AutoSubmitForm } from "./auto-submit-form";
import { handleServerActionResponse } from "@/lib/client-utils";
import { completeFlowOrGetUrl } from "@/lib/client";
type Props = {
loginName?: string;
+1 -1
View File
@@ -12,7 +12,7 @@ import { useRouter } from "next/navigation";
import React, { useState } from "react";
import { Avatar } from "./avatar";
import { Translated } from "./translated";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { AutoSubmitForm } from "./auto-submit-form";
export function isSessionValid(session: Partial<Session>): {
@@ -16,7 +16,7 @@ import { TextInput } from "./input";
import { PasswordComplexity } from "./password-complexity";
import { Spinner } from "./spinner";
import { Translated } from "./translated";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { AutoSubmitForm } from "./auto-submit-form";
type Inputs =
@@ -2,7 +2,7 @@
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
import { registerUser } from "@/lib/server/register";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
import { useRouter } from "next/navigation";
import { useState } from "react";
+2 -1
View File
@@ -1,6 +1,5 @@
"use client";
import { completeFlowOrGetUrl, handleServerActionResponse } from "@/lib/client";
import { verifyTOTP } from "@/lib/server/verify";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import Link from "next/link";
@@ -16,6 +15,8 @@ import { TextInput } from "./input";
import { Spinner } from "./spinner";
import { Translated } from "./translated";
import { AutoSubmitForm } from "./auto-submit-form";
import { handleServerActionResponse } from "@/lib/client-utils";
import { completeFlowOrGetUrl } from "@/lib/client";
type Inputs = {
code: string;
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { sendLoginname } from "@/lib/server/loginname";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
+1 -1
View File
@@ -2,7 +2,7 @@
import { Alert, AlertType } from "@/components/alert";
import { resendVerification, sendVerification } from "@/lib/server/verify";
import { handleServerActionResponse } from "@/lib/client";
import { handleServerActionResponse } from "@/lib/client-utils";
import { UNKNOWN_USER_ID } from "@/lib/constants";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";