mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 09:54:00 +00:00
fix webauthn flow, state
This commit is contained in:
@@ -13,7 +13,7 @@ export default async function Page({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const { loginName, altPassword } = searchParams;
|
const { loginName, altPassword, authRequestId } = searchParams;
|
||||||
|
|
||||||
const sessionFactors = await loadSession(loginName);
|
const sessionFactors = await loadSession(loginName);
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ export default async function Page({
|
|||||||
{loginName && (
|
{loginName && (
|
||||||
<LoginPasskey
|
<LoginPasskey
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
|
authRequestId={authRequestId}
|
||||||
altPassword={altPassword === "true"}
|
altPassword={altPassword === "true"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default async function Page({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Record<string | number | symbol, string | undefined>;
|
searchParams: Record<string | number | symbol, string | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const { loginName, promptPasswordless, alt } = searchParams;
|
const { loginName, promptPasswordless, authRequestId, alt } = searchParams;
|
||||||
const sessionFactors = await loadSession(loginName);
|
const sessionFactors = await loadSession(loginName);
|
||||||
|
|
||||||
async function loadSession(loginName?: string) {
|
async function loadSession(loginName?: string) {
|
||||||
@@ -46,6 +46,7 @@ export default async function Page({
|
|||||||
|
|
||||||
<PasswordForm
|
<PasswordForm
|
||||||
loginName={loginName}
|
loginName={loginName}
|
||||||
|
authRequestId={authRequestId}
|
||||||
promptPasswordless={promptPasswordless === "true"}
|
promptPasswordless={promptPasswordless === "true"}
|
||||||
isAlternative={alt === "true"}
|
isAlternative={alt === "true"}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -45,12 +45,11 @@ export async function POST(request: NextRequest) {
|
|||||||
if (body) {
|
if (body) {
|
||||||
const { loginName, authRequestId } = body;
|
const { loginName, authRequestId } = body;
|
||||||
|
|
||||||
const domain: string = request.nextUrl.hostname;
|
// const domain: string = request.nextUrl.hostname;
|
||||||
|
|
||||||
return createSessionAndUpdateCookie(
|
return createSessionAndUpdateCookie(
|
||||||
loginName,
|
loginName,
|
||||||
undefined,
|
undefined,
|
||||||
domain,
|
|
||||||
undefined,
|
undefined,
|
||||||
authRequestId
|
authRequestId
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
|
|||||||
return createSessionAndUpdateCookie(
|
return createSessionAndUpdateCookie(
|
||||||
loginName,
|
loginName,
|
||||||
password,
|
password,
|
||||||
domain,
|
undefined,
|
||||||
undefined
|
undefined
|
||||||
).then((session) => {
|
).then((session) => {
|
||||||
return NextResponse.json(session);
|
return NextResponse.json(session);
|
||||||
@@ -44,7 +44,7 @@ export async function PUT(request: NextRequest) {
|
|||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
||||||
if (body) {
|
if (body) {
|
||||||
const { loginName, password, challenges, passkey } = body;
|
const { loginName, password, challenges, passkey, authRequestId } = body;
|
||||||
|
|
||||||
const recentPromise: Promise<SessionCookie> = loginName
|
const recentPromise: Promise<SessionCookie> = loginName
|
||||||
? getSessionCookieByLoginName(loginName).catch((error) => {
|
? getSessionCookieByLoginName(loginName).catch((error) => {
|
||||||
@@ -54,7 +54,7 @@ export async function PUT(request: NextRequest) {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const domain: string = request.nextUrl.hostname;
|
// const domain: string = request.nextUrl.hostname;
|
||||||
|
|
||||||
return recentPromise
|
return recentPromise
|
||||||
.then((recent) => {
|
.then((recent) => {
|
||||||
@@ -64,8 +64,8 @@ export async function PUT(request: NextRequest) {
|
|||||||
recent.loginName,
|
recent.loginName,
|
||||||
password,
|
password,
|
||||||
passkey,
|
passkey,
|
||||||
domain,
|
challenges,
|
||||||
challenges
|
authRequestId
|
||||||
).then((session) => {
|
).then((session) => {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export async function createSession(
|
|||||||
server: ZitadelServer,
|
server: ZitadelServer,
|
||||||
loginName: string,
|
loginName: string,
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
challenges: RequestChallenges
|
challenges: RequestChallenges | undefined
|
||||||
): Promise<CreateSessionResponse | undefined> {
|
): Promise<CreateSessionResponse | undefined> {
|
||||||
const sessionService = session.getSession(server);
|
const sessionService = session.getSession(server);
|
||||||
return password
|
return password
|
||||||
@@ -125,7 +125,7 @@ export async function setSession(
|
|||||||
sessionToken: string,
|
sessionToken: string,
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
webAuthN: { credentialAssertionData: any } | undefined,
|
webAuthN: { credentialAssertionData: any } | undefined,
|
||||||
challenges: RequestChallenges
|
challenges: RequestChallenges | undefined
|
||||||
): Promise<SetSessionResponse | undefined> {
|
): Promise<SetSessionResponse | undefined> {
|
||||||
const sessionService = session.getSession(server);
|
const sessionService = session.getSession(server);
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ import { Spinner } from "./Spinner";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loginName: string;
|
loginName: string;
|
||||||
|
authRequestId?: string;
|
||||||
altPassword: boolean;
|
altPassword: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LoginPasskey({ loginName, altPassword }: Props) {
|
export default function LoginPasskey({
|
||||||
|
loginName,
|
||||||
|
authRequestId,
|
||||||
|
altPassword,
|
||||||
|
}: Props) {
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -60,6 +65,7 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName,
|
loginName,
|
||||||
challenges: [1], // request passkey challenge
|
challenges: [1], // request passkey challenge
|
||||||
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,6 +87,7 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName,
|
loginName,
|
||||||
passkey: data,
|
passkey: data,
|
||||||
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -168,11 +175,16 @@ export default function LoginPasskey({ loginName, altPassword }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant={ButtonVariants.Secondary}
|
variant={ButtonVariants.Secondary}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
router.push(
|
const params = { loginName, alt: "true" };
|
||||||
"/password?" + new URLSearchParams({ loginName, alt: "true" }) // alt is set because password is requested as alternative auth method, so passwordless prompt can be escaped
|
|
||||||
)
|
return router.push(
|
||||||
}
|
"/password?" +
|
||||||
|
new URLSearchParams(
|
||||||
|
authRequestId ? { ...params, authRequestId } : params
|
||||||
|
) // alt is set because password is requested as alternative auth method, so passwordless prompt can be escaped
|
||||||
|
);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
use password
|
use password
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ type Inputs = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loginName?: string;
|
loginName?: string;
|
||||||
|
authRequestId?: string;
|
||||||
isAlternative?: boolean; // whether password was requested as alternative auth method
|
isAlternative?: boolean; // whether password was requested as alternative auth method
|
||||||
promptPasswordless?: boolean;
|
promptPasswordless?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PasswordForm({
|
export default function PasswordForm({
|
||||||
loginName,
|
loginName,
|
||||||
|
authRequestId,
|
||||||
promptPasswordless,
|
promptPasswordless,
|
||||||
isAlternative,
|
isAlternative,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
@@ -44,6 +46,7 @@ export default function PasswordForm({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName,
|
loginName,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
|
authRequestId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export default function RegisterFormWithoutPassword({ legal }: Props) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName: loginName,
|
loginName: loginName,
|
||||||
|
// authRequestId, register does not need an oidc callback at the end
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function SessionItem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validPassword = session?.factors?.password?.verifiedAt;
|
const validPassword = session?.factors?.password?.verifiedAt;
|
||||||
const validPasskey = session?.factors?.passkey?.verifiedAt;
|
const validPasskey = session?.factors?.webAuthN?.verifiedAt;
|
||||||
|
|
||||||
const validUser = validPassword || validPasskey;
|
const validUser = validPassword || validPasskey;
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export default function SetPasswordForm({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
loginName: loginName,
|
loginName: loginName,
|
||||||
password: password,
|
password: password,
|
||||||
|
// authRequestId, register does not need an oidc callback
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -65,16 +65,18 @@ export default function UsernameForm({
|
|||||||
const method = response.authMethodTypes[0];
|
const method = response.authMethodTypes[0];
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 1: //AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSWORD:
|
case 1: //AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSWORD:
|
||||||
|
const paramsPassword: any = { loginName: values.loginName };
|
||||||
|
|
||||||
|
if (loginSettings?.passkeysType === 1) {
|
||||||
|
paramsPassword.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
paramsPassword.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
return router.push(
|
return router.push(
|
||||||
"/password?" +
|
"/password?" + new URLSearchParams(paramsPassword)
|
||||||
new URLSearchParams(
|
|
||||||
loginSettings?.passkeysType === 1
|
|
||||||
? {
|
|
||||||
loginName: values.loginName,
|
|
||||||
promptPasswordless: `true`, // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
|
||||||
}
|
|
||||||
: { loginName: values.loginName }
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
case 2: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
|
case 2: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
|
||||||
return router.push(
|
return router.push(
|
||||||
@@ -82,16 +84,17 @@ export default function UsernameForm({
|
|||||||
new URLSearchParams({ loginName: values.loginName })
|
new URLSearchParams({ loginName: values.loginName })
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
const paramsPasskey: any = { loginName: values.loginName };
|
||||||
|
|
||||||
|
if (loginSettings?.passkeysType === 1) {
|
||||||
|
paramsPasskey.promptPasswordless = `true`; // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
paramsPasskey.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
return router.push(
|
return router.push(
|
||||||
"/password?" +
|
"/password?" + new URLSearchParams(paramsPasskey)
|
||||||
new URLSearchParams(
|
|
||||||
loginSettings?.passkeysType === 1
|
|
||||||
? {
|
|
||||||
loginName: values.loginName,
|
|
||||||
promptPasswordless: `true`, // PasskeysType.PASSKEYS_TYPE_ALLOWED,
|
|
||||||
}
|
|
||||||
: { loginName: values.loginName }
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
|
|||||||
@@ -4,19 +4,17 @@ import {
|
|||||||
addSessionToCookie,
|
addSessionToCookie,
|
||||||
updateSessionCookie,
|
updateSessionCookie,
|
||||||
} from "./cookies";
|
} from "./cookies";
|
||||||
import { ChallengeKind, Session, Challenges } from "@zitadel/server";
|
import { Session, Challenges, RequestChallenges } from "@zitadel/server";
|
||||||
|
|
||||||
export async function createSessionAndUpdateCookie(
|
export async function createSessionAndUpdateCookie(
|
||||||
loginName: string,
|
loginName: string,
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
domain: string,
|
challenges: RequestChallenges | undefined,
|
||||||
challenges: ChallengeKind[] | undefined,
|
|
||||||
authRequestId: string | undefined
|
authRequestId: string | undefined
|
||||||
): Promise<Session> {
|
): Promise<Session> {
|
||||||
const createdSession = await createSession(
|
const createdSession = await createSession(
|
||||||
server,
|
server,
|
||||||
loginName,
|
loginName,
|
||||||
domain,
|
|
||||||
password,
|
password,
|
||||||
challenges
|
challenges
|
||||||
);
|
);
|
||||||
@@ -61,15 +59,13 @@ export async function setSessionAndUpdateCookie(
|
|||||||
loginName: string,
|
loginName: string,
|
||||||
password: string | undefined,
|
password: string | undefined,
|
||||||
passkey: { credentialAssertionData: any } | undefined,
|
passkey: { credentialAssertionData: any } | undefined,
|
||||||
domain: string | undefined,
|
challenges: RequestChallenges | undefined,
|
||||||
challenges: ChallengeKind[] | undefined,
|
|
||||||
authRequestId: string | undefined
|
authRequestId: string | undefined
|
||||||
): Promise<SessionWithChallenges> {
|
): Promise<SessionWithChallenges> {
|
||||||
return setSession(
|
return setSession(
|
||||||
server,
|
server,
|
||||||
sessionId,
|
sessionId,
|
||||||
sessionToken,
|
sessionToken,
|
||||||
domain,
|
|
||||||
password,
|
password,
|
||||||
passkey,
|
passkey,
|
||||||
challenges
|
challenges
|
||||||
|
|||||||
Reference in New Issue
Block a user