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.
This commit is contained in:
Max Peintner
2026-02-25 14:12:23 +01:00
committed by GitHub
parent c8abf6de74
commit 3b74b86dc4
3 changed files with 22 additions and 10 deletions
+11 -2
View File
@@ -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,
+11 -2
View File
@@ -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: {
-6
View File
@@ -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,