From 3b74b86dc411efec24592f7ab12a9e431c78d195 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Wed, 25 Feb 2026 14:12:23 +0100 Subject: [PATCH] fix(login): check for primary auth methods on user discovery (#11689) # Which Problems Are Solved - Currently, if a user has only secondary authentication methods (like TOTP) configured but no primary authentication method (Password, Passkey, or IDP), the login flow fails to handle them correctly, resulting in an unhelpful "user not found" error when they attempt to log in. - These users are not correctly prompted to set up a primary authentication factor, leaving them in a state where they cannot successfully authenticate. # How the Problems Are Solved - Updated `sendLoginname` to evaluate if a user has an active primary authentication method (`AuthenticationMethodType.PASSWORD`, `PASSKEY`, or `IDP`. If they lack a primary method, the flow now correctly redirects them into the verification/invite process rather than falling through the method checks and returning a generic error. - Updated `sendVerification` to replace the empty `authMethodTypes` array check with a specific check for primary authentication methods. Now, after completing email/invite verification, users who do not have a primary authentication method are explicitly redirected to `/authenticator/set` to set one up. --- apps/login/src/lib/server/loginname.ts | 13 +++++++++++-- apps/login/src/lib/server/verify.ts | 13 +++++++++++-- apps/login/src/lib/service-url.ts | 6 ------ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 0ed1ad052..1d2b9f085 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -100,6 +100,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { return { redirect: "/password?" + paramsPasswordDefault }; } + return { error: t("errors.userNotFound") }; }; @@ -320,8 +321,16 @@ export async function sendLoginname(command: SendLoginnameCommand) { userId: session?.factors?.user?.id ?? userId, }); - // always resend invite if user has no auth method set - if (!methods.authMethodTypes || !methods.authMethodTypes.length) { + const hasPrimaryMethod = + methods.authMethodTypes?.some( + (m: AuthenticationMethodType) => + m === AuthenticationMethodType.PASSWORD || + m === AuthenticationMethodType.PASSKEY || + m === AuthenticationMethodType.IDP, + ) ?? false; + + // always resend invite or setup email if user has no primary auth method set + if (!hasPrimaryMethod) { console.log("humanUser.email?.isVerified", humanUser?.email?.isVerified); const params = new URLSearchParams({ loginName: (session?.factors?.user?.loginName ?? user.preferredLoginName) as string, diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index 00adc1c6a..f687811c7 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -16,6 +16,7 @@ import crypto from "crypto"; import { create } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { cookies, headers } from "next/headers"; import { completeFlowOrGetUrl } from "../client"; import { getSessionCookieByLoginName } from "../cookies"; @@ -113,8 +114,16 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { return { error: t("errors.couldNotLoadAuthenticators") }; } - // if no authmethods are found on the user, redirect to set one up - if (authMethodResponse && authMethodResponse.authMethodTypes && authMethodResponse.authMethodTypes.length == 0) { + const hasPrimaryMethod = + authMethodResponse?.authMethodTypes?.some( + (m: AuthenticationMethodType) => + m === AuthenticationMethodType.PASSWORD || + m === AuthenticationMethodType.PASSKEY || + m === AuthenticationMethodType.IDP, + ) ?? false; + + // if no primary auth methods are found on the user, redirect to set one up + if (!hasPrimaryMethod) { if (!session) { const checks = create(ChecksSchema, { user: { diff --git a/apps/login/src/lib/service-url.ts b/apps/login/src/lib/service-url.ts index ed9aa059c..02a2160de 100644 --- a/apps/login/src/lib/service-url.ts +++ b/apps/login/src/lib/service-url.ts @@ -29,12 +29,6 @@ export function getServiceConfig(headers: ReadonlyHeaders): { serviceConfig: Ser const instanceHost = getInstanceHost(headers); const publicHost = getPublicHost(headers); - console.log("serviceConfig", { - baseUrl: process.env.ZITADEL_API_URL, - ...(instanceHost && { instanceHost: stripProtocol(instanceHost) }), - ...(publicHost && { publicHost: stripProtocol(publicHost) }), - }); - return { serviceConfig: { baseUrl: process.env.ZITADEL_API_URL,