mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 02:57:32 +00:00
fix(login): encode formpost data to cookie (#10173)
This PR implements a SAML cookie which is used to save information to complete the form post. It is primarily used to avoid sending the information as url search params and therefore reducing its length.
This commit is contained in:
@@ -1,22 +1,41 @@
|
||||
import { getSAMLFormCookie } from "@/lib/saml";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const url = searchParams.get("url");
|
||||
const relayState = searchParams.get("RelayState");
|
||||
const samlResponse = searchParams.get("SAMLResponse");
|
||||
const id = searchParams.get("id");
|
||||
|
||||
if (!url || !relayState || !samlResponse) {
|
||||
return new NextResponse("Missing required parameters", { status: 400 });
|
||||
if (!url) {
|
||||
return new NextResponse("Missing url parameter", { status: 400 });
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
return new NextResponse("Missing id parameter", { status: 400 });
|
||||
}
|
||||
|
||||
const formData = await getSAMLFormCookie(id);
|
||||
|
||||
const formDataParsed = formData ? JSON.parse(formData) : null;
|
||||
|
||||
if (!formDataParsed) {
|
||||
return new NextResponse("SAML form data not found", { status: 404 });
|
||||
}
|
||||
|
||||
// Generate hidden input fields for all key-value pairs in formDataParsed
|
||||
const hiddenInputs = Object.entries(formDataParsed)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`<input type="hidden" name="${key}" value="${value}" />`,
|
||||
)
|
||||
.join("\n ");
|
||||
|
||||
// Respond with an HTML form that auto-submits via POST
|
||||
const html = `
|
||||
<html>
|
||||
<body onload="document.forms[0].submit()">
|
||||
<form action="${url}" method="post">
|
||||
<input type="hidden" name="RelayState" value="${relayState}" />
|
||||
<input type="hidden" name="SAMLResponse" value="${samlResponse}" />
|
||||
${hiddenInputs}
|
||||
<noscript>
|
||||
<button type="submit">Continue</button>
|
||||
</noscript>
|
||||
|
@@ -520,16 +520,24 @@ export async function GET(request: NextRequest) {
|
||||
if (url && binding.case === "redirect") {
|
||||
return NextResponse.redirect(url);
|
||||
} else if (url && binding.case === "post") {
|
||||
const redirectUrl = constructUrl(request, "/saml-post");
|
||||
// Create HTML form that auto-submits via POST and escape the SAML cookie
|
||||
const html = `
|
||||
<html>
|
||||
<body onload="document.forms[0].submit()">
|
||||
<form action="${url}" method="post">
|
||||
<input type="hidden" name="RelayState" value="${binding.value.relayState}" />
|
||||
<input type="hidden" name="SAMLResponse" value="${binding.value.samlResponse}" />
|
||||
<noscript>
|
||||
<button type="submit">Continue</button>
|
||||
</noscript>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
redirectUrl.searchParams.set("url", url);
|
||||
redirectUrl.searchParams.set("RelayState", binding.value.relayState);
|
||||
redirectUrl.searchParams.set(
|
||||
"SAMLResponse",
|
||||
binding.value.samlResponse,
|
||||
);
|
||||
|
||||
return NextResponse.redirect(redirectUrl.toString());
|
||||
return new NextResponse(html, {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
"could not create response, redirect user to choose other account",
|
||||
|
@@ -4,7 +4,9 @@ import { createResponse, getLoginSettings } from "@/lib/zitadel";
|
||||
import { create } from "@zitadel/client";
|
||||
import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { constructUrl } from "./service-url";
|
||||
import { isSessionValid } from "./session";
|
||||
|
||||
@@ -17,6 +19,37 @@ type LoginWithSAMLAndSession = {
|
||||
request: NextRequest;
|
||||
};
|
||||
|
||||
export async function getSAMLFormUID() {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export async function setSAMLFormCookie(value: string): Promise<string> {
|
||||
const cookiesList = await cookies();
|
||||
|
||||
const uid = await getSAMLFormUID();
|
||||
|
||||
await cookiesList.set({
|
||||
name: uid,
|
||||
value: value,
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
maxAge: 5 * 60, // 5 minutes
|
||||
});
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
export async function getSAMLFormCookie(uid: string): Promise<string | null> {
|
||||
const cookiesList = await cookies();
|
||||
|
||||
const cookie = cookiesList.get(uid);
|
||||
if (!cookie || !cookie.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cookie.value;
|
||||
}
|
||||
|
||||
export async function loginWithSAMLAndSession({
|
||||
serviceUrl,
|
||||
samlRequest,
|
||||
|
@@ -52,6 +52,7 @@ import {
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { unstable_cacheLife as cacheLife } from "next/cache";
|
||||
import { getUserAgent } from "./fingerprint";
|
||||
import { setSAMLFormCookie } from "./saml";
|
||||
import { createServiceForHost } from "./service";
|
||||
|
||||
const useCache = process.env.DEBUG !== "true";
|
||||
@@ -981,18 +982,15 @@ export async function startIdentityProviderFlow({
|
||||
value: urls,
|
||||
},
|
||||
})
|
||||
.then((resp) => {
|
||||
.then(async (resp) => {
|
||||
if (resp.nextStep.case === "authUrl" && resp.nextStep.value) {
|
||||
return resp.nextStep.value;
|
||||
} else if (resp.nextStep.case === "formData" && resp.nextStep.value) {
|
||||
const formData: FormData = resp.nextStep.value;
|
||||
const redirectUrl = "/saml-post";
|
||||
|
||||
const params = new URLSearchParams({ url: formData.url });
|
||||
|
||||
Object.entries(formData.fields).forEach(([k, v]) => {
|
||||
params.append(k, v);
|
||||
});
|
||||
const dataId = await setSAMLFormCookie(JSON.stringify(formData.fields));
|
||||
const params = new URLSearchParams({ url: formData.url, id: dataId });
|
||||
|
||||
return `${redirectUrl}?${params.toString()}`;
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user