Proxy rotating session nonce cookies
This commit is contained in:
parent
f0e6405b11
commit
edbcbea5b4
@ -4,11 +4,11 @@ import { setAuthCookies } from "@/lib/auth-cookies";
|
|||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
const res = await proxyRequest(req, "auth/login");
|
const res = await proxyRequest(req, "auth/login");
|
||||||
const payload = await res.clone().json().catch(() => null) as { data?: { accessToken?: string; refreshToken?: string; [key: string]: unknown } } | null;
|
const payload = await res.clone().json().catch(() => null) as { data?: { accessToken?: string; refreshToken?: string; sessionNonce?: string; [key: string]: unknown } } | null;
|
||||||
if (res.ok && payload?.data?.accessToken && payload.data.refreshToken) {
|
if (res.ok && payload?.data?.accessToken && payload.data.refreshToken && payload.data.sessionNonce) {
|
||||||
const { accessToken, refreshToken, ...safeData } = payload.data;
|
const { accessToken, refreshToken, sessionNonce, ...safeData } = payload.data;
|
||||||
const safeRes = NextResponse.json({ ...payload, data: safeData }, { status: res.status });
|
const safeRes = NextResponse.json({ ...payload, data: safeData }, { status: res.status });
|
||||||
setAuthCookies(safeRes, accessToken, refreshToken);
|
setAuthCookies(safeRes, accessToken, refreshToken, sessionNonce);
|
||||||
return safeRes;
|
return safeRes;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { getBackendUrl } from "@/lib/backend";
|
import { getBackendUrl } from "@/lib/backend";
|
||||||
import { REFRESH_COOKIE, clearAuthCookies } from "@/lib/auth-cookies";
|
import { ACCESS_COOKIE, NONCE_COOKIE, REFRESH_COOKIE, clearAuthCookies } from "@/lib/auth-cookies";
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
|
const accessToken = req.cookies.get(ACCESS_COOKIE)?.value;
|
||||||
const refreshToken = req.cookies.get(REFRESH_COOKIE)?.value;
|
const refreshToken = req.cookies.get(REFRESH_COOKIE)?.value;
|
||||||
|
const sessionNonce = req.cookies.get(NONCE_COOKIE)?.value;
|
||||||
const backendRes = refreshToken
|
const backendRes = refreshToken
|
||||||
? await fetch(getBackendUrl("auth/logout"), {
|
? await fetch(getBackendUrl("auth/logout"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
||||||
|
...(sessionNonce ? { "X-LedgerOne-Session-Nonce": sessionNonce } : {}),
|
||||||
"User-Agent": req.headers.get("user-agent") ?? "",
|
"User-Agent": req.headers.get("user-agent") ?? "",
|
||||||
"X-Forwarded-For": req.headers.get("x-forwarded-for") ?? "",
|
"X-Forwarded-For": req.headers.get("x-forwarded-for") ?? "",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { getBackendUrl } from "@/lib/backend";
|
import { getBackendUrl } from "@/lib/backend";
|
||||||
import { REFRESH_COOKIE, clearAuthCookies, setAuthCookies } from "@/lib/auth-cookies";
|
import { NONCE_COOKIE, REFRESH_COOKIE, clearAuthCookies, setAuthCookies } from "@/lib/auth-cookies";
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
const refreshToken = req.cookies.get(REFRESH_COOKIE)?.value;
|
const refreshToken = req.cookies.get(REFRESH_COOKIE)?.value;
|
||||||
|
const sessionNonce = req.cookies.get(NONCE_COOKIE)?.value;
|
||||||
if (!refreshToken) {
|
if (!refreshToken) {
|
||||||
const res = NextResponse.json(
|
const res = NextResponse.json(
|
||||||
{
|
{
|
||||||
@ -23,20 +24,21 @@ export async function POST(req: NextRequest) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"User-Agent": req.headers.get("user-agent") ?? "",
|
"User-Agent": req.headers.get("user-agent") ?? "",
|
||||||
"X-Forwarded-For": req.headers.get("x-forwarded-for") ?? "",
|
"X-Forwarded-For": req.headers.get("x-forwarded-for") ?? "",
|
||||||
|
...(sessionNonce ? { "X-LedgerOne-Session-Nonce": sessionNonce } : {}),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ refreshToken }),
|
body: JSON.stringify({ refreshToken }),
|
||||||
});
|
});
|
||||||
const payload = await backendRes.text();
|
const payload = await backendRes.text();
|
||||||
const parsed = JSON.parse(payload) as { data?: { accessToken?: string; refreshToken?: string; [key: string]: unknown } };
|
const parsed = JSON.parse(payload) as { data?: { accessToken?: string; refreshToken?: string; sessionNonce?: string; [key: string]: unknown } };
|
||||||
const responsePayload = backendRes.ok && parsed.data?.accessToken && parsed.data.refreshToken
|
const responsePayload = backendRes.ok && parsed.data?.accessToken && parsed.data.refreshToken && parsed.data.sessionNonce
|
||||||
? JSON.stringify({ ...parsed, data: {} })
|
? JSON.stringify({ ...parsed, data: {} })
|
||||||
: payload;
|
: payload;
|
||||||
const res = new NextResponse(responsePayload, {
|
const res = new NextResponse(responsePayload, {
|
||||||
status: backendRes.status,
|
status: backendRes.status,
|
||||||
headers: { "Content-Type": backendRes.headers.get("content-type") ?? "application/json" },
|
headers: { "Content-Type": backendRes.headers.get("content-type") ?? "application/json" },
|
||||||
});
|
});
|
||||||
if (backendRes.ok && parsed.data?.accessToken && parsed.data.refreshToken) {
|
if (backendRes.ok && parsed.data?.accessToken && parsed.data.refreshToken && parsed.data.sessionNonce) {
|
||||||
setAuthCookies(res, parsed.data.accessToken, parsed.data.refreshToken);
|
setAuthCookies(res, parsed.data.accessToken, parsed.data.refreshToken, parsed.data.sessionNonce);
|
||||||
} else {
|
} else {
|
||||||
clearAuthCookies(res);
|
clearAuthCookies(res);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import { setAuthCookies } from "@/lib/auth-cookies";
|
|||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
const res = await proxyRequest(req, "auth/register");
|
const res = await proxyRequest(req, "auth/register");
|
||||||
const payload = await res.clone().json().catch(() => null) as { data?: { accessToken?: string; refreshToken?: string; [key: string]: unknown } } | null;
|
const payload = await res.clone().json().catch(() => null) as { data?: { accessToken?: string; refreshToken?: string; sessionNonce?: string; [key: string]: unknown } } | null;
|
||||||
if (res.ok && payload?.data?.accessToken && payload.data.refreshToken) {
|
if (res.ok && payload?.data?.accessToken && payload.data.refreshToken && payload.data.sessionNonce) {
|
||||||
const { accessToken, refreshToken, ...safeData } = payload.data;
|
const { accessToken, refreshToken, sessionNonce, ...safeData } = payload.data;
|
||||||
const safeRes = NextResponse.json({ ...payload, data: safeData }, { status: res.status });
|
const safeRes = NextResponse.json({ ...payload, data: safeData }, { status: res.status });
|
||||||
setAuthCookies(safeRes, accessToken, refreshToken);
|
setAuthCookies(safeRes, accessToken, refreshToken, sessionNonce);
|
||||||
return safeRes;
|
return safeRes;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -7,11 +7,11 @@ export async function POST(
|
|||||||
{ params }: { params: { provider: string } },
|
{ params }: { params: { provider: string } },
|
||||||
) {
|
) {
|
||||||
const res = await proxyRequest(req, `auth/social/${params.provider}/callback`);
|
const res = await proxyRequest(req, `auth/social/${params.provider}/callback`);
|
||||||
const payload = await res.clone().json().catch(() => null) as { data?: { accessToken?: string; refreshToken?: string; [key: string]: unknown } } | null;
|
const payload = await res.clone().json().catch(() => null) as { data?: { accessToken?: string; refreshToken?: string; sessionNonce?: string; [key: string]: unknown } } | null;
|
||||||
if (res.ok && payload?.data?.accessToken && payload.data.refreshToken) {
|
if (res.ok && payload?.data?.accessToken && payload.data.refreshToken && payload.data.sessionNonce) {
|
||||||
const { accessToken, refreshToken, ...safeData } = payload.data;
|
const { accessToken, refreshToken, sessionNonce, ...safeData } = payload.data;
|
||||||
const safeRes = NextResponse.json({ ...payload, data: safeData }, { status: res.status });
|
const safeRes = NextResponse.json({ ...payload, data: safeData }, { status: res.status });
|
||||||
setAuthCookies(safeRes, accessToken, refreshToken);
|
setAuthCookies(safeRes, accessToken, refreshToken, sessionNonce);
|
||||||
return safeRes;
|
return safeRes;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const IS_PROD = process.env.NODE_ENV === "production";
|
|||||||
export const ACCESS_COOKIE = "ledgerone_access";
|
export const ACCESS_COOKIE = "ledgerone_access";
|
||||||
export const REFRESH_COOKIE = "ledgerone_refresh";
|
export const REFRESH_COOKIE = "ledgerone_refresh";
|
||||||
export const AUTH_COOKIE = "ledgerone_auth";
|
export const AUTH_COOKIE = "ledgerone_auth";
|
||||||
|
export const NONCE_COOKIE = "ledgerone_nonce";
|
||||||
|
|
||||||
const cookieOptions = {
|
const cookieOptions = {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
@ -13,7 +14,7 @@ const cookieOptions = {
|
|||||||
path: "/",
|
path: "/",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setAuthCookies(res: NextResponse, accessToken: string, refreshToken: string) {
|
export function setAuthCookies(res: NextResponse, accessToken: string, refreshToken: string, sessionNonce?: string) {
|
||||||
res.cookies.set(ACCESS_COOKIE, accessToken, {
|
res.cookies.set(ACCESS_COOKIE, accessToken, {
|
||||||
...cookieOptions,
|
...cookieOptions,
|
||||||
maxAge: 60,
|
maxAge: 60,
|
||||||
@ -26,10 +27,16 @@ export function setAuthCookies(res: NextResponse, accessToken: string, refreshTo
|
|||||||
...cookieOptions,
|
...cookieOptions,
|
||||||
maxAge: 30 * 24 * 60 * 60,
|
maxAge: 30 * 24 * 60 * 60,
|
||||||
});
|
});
|
||||||
|
if (sessionNonce) {
|
||||||
|
res.cookies.set(NONCE_COOKIE, sessionNonce, {
|
||||||
|
...cookieOptions,
|
||||||
|
maxAge: 30 * 24 * 60 * 60,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearAuthCookies(res: NextResponse) {
|
export function clearAuthCookies(res: NextResponse) {
|
||||||
for (const name of [ACCESS_COOKIE, REFRESH_COOKIE, AUTH_COOKIE]) {
|
for (const name of [ACCESS_COOKIE, REFRESH_COOKIE, AUTH_COOKIE, NONCE_COOKIE]) {
|
||||||
res.cookies.set(name, "", {
|
res.cookies.set(name, "", {
|
||||||
...cookieOptions,
|
...cookieOptions,
|
||||||
maxAge: 0,
|
maxAge: 0,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// Forwards requests to the NestJS backend, including the Bearer token.
|
// Forwards requests to the NestJS backend, including the Bearer token.
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { ACCESS_COOKIE } from "./auth-cookies";
|
import { ACCESS_COOKIE, NONCE_COOKIE } from "./auth-cookies";
|
||||||
|
|
||||||
const BASE_URL = process.env.LEDGERONE_API_URL ?? "http://localhost:3051";
|
const BASE_URL = process.env.LEDGERONE_API_URL ?? "http://localhost:3051";
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ export async function proxyRequest(
|
|||||||
const method = options.method ?? req.method;
|
const method = options.method ?? req.method;
|
||||||
const auth = req.headers.get("authorization") ?? "";
|
const auth = req.headers.get("authorization") ?? "";
|
||||||
const cookieAccessToken = req.cookies.get(ACCESS_COOKIE)?.value ?? "";
|
const cookieAccessToken = req.cookies.get(ACCESS_COOKIE)?.value ?? "";
|
||||||
|
const sessionNonce = req.cookies.get(NONCE_COOKIE)?.value ?? "";
|
||||||
const contentType = req.headers.get("content-type") ?? "";
|
const contentType = req.headers.get("content-type") ?? "";
|
||||||
const userAgent = req.headers.get("user-agent") ?? "";
|
const userAgent = req.headers.get("user-agent") ?? "";
|
||||||
const forwardedFor = req.headers.get("x-forwarded-for") ?? "";
|
const forwardedFor = req.headers.get("x-forwarded-for") ?? "";
|
||||||
@ -36,6 +37,7 @@ export async function proxyRequest(
|
|||||||
const headers: Record<string, string> = { ...options.extraHeaders };
|
const headers: Record<string, string> = { ...options.extraHeaders };
|
||||||
if (auth) headers["Authorization"] = auth;
|
if (auth) headers["Authorization"] = auth;
|
||||||
else if (cookieAccessToken) headers["Authorization"] = `Bearer ${cookieAccessToken}`;
|
else if (cookieAccessToken) headers["Authorization"] = `Bearer ${cookieAccessToken}`;
|
||||||
|
if (sessionNonce) headers["X-LedgerOne-Session-Nonce"] = sessionNonce;
|
||||||
if (userAgent) headers["User-Agent"] = userAgent;
|
if (userAgent) headers["User-Agent"] = userAgent;
|
||||||
if (forwardedFor) headers["X-Forwarded-For"] = forwardedFor;
|
if (forwardedFor) headers["X-Forwarded-For"] = forwardedFor;
|
||||||
|
|
||||||
@ -61,12 +63,23 @@ export async function proxyRequest(
|
|||||||
body: body ?? undefined,
|
body: body ?? undefined,
|
||||||
});
|
});
|
||||||
const payload = await res.text();
|
const payload = await res.text();
|
||||||
return new NextResponse(payload, {
|
const response = new NextResponse(payload, {
|
||||||
status: res.status,
|
status: res.status,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": res.headers.get("content-type") ?? "application/json",
|
"Content-Type": res.headers.get("content-type") ?? "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const nextNonce = res.headers.get("x-ledgerone-next-nonce");
|
||||||
|
if (nextNonce) {
|
||||||
|
response.cookies.set(NONCE_COOKIE, nextNonce, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
sameSite: "lax",
|
||||||
|
path: "/",
|
||||||
|
maxAge: 30 * 24 * 60 * 60,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response;
|
||||||
} catch {
|
} catch {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user