Skip to content

Commit

Permalink
🐛 Fix infinite loop when trying to migrate legacy cookie
Browse files Browse the repository at this point in the history
  • Loading branch information
lukevella committed Feb 12, 2025
1 parent 8519631 commit a5a45fe
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 41 deletions.
80 changes: 45 additions & 35 deletions apps/web/src/auth/legacy/next-auth-cookie-migration.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { absoluteUrl } from "@rallly/utils/absolute-url";
import type { NextRequest, NextResponse } from "next/server";
import { encode } from "next-auth/jwt";

import { decodeLegacyJWT } from "./helpers/jwt";

const isSecureCookie =
process.env.NEXT_PUBLIC_BASE_URL?.startsWith("https://") ?? false;
const isSecureCookie = absoluteUrl().startsWith("https://");

const prefix = isSecureCookie ? "__Secure-" : "";

Expand All @@ -17,49 +16,60 @@ const newCookieName = prefix + "authjs.session-token";
* This is needed for next-auth v5 which renamed the cookie prefix from 'next-auth' to 'authjs'
*/
export function withAuthMigration(
middleware: (req: NextRequest) => void | Response | Promise<void | Response>,
middleware: (req: NextRequest) => Promise<NextResponse>,
) {
async function runMiddlewareAndDeleteOldCookie(req: NextRequest) {
const res = await middleware(req);
res.cookies.set(oldCookieName, "", {
httpOnly: true,
secure: isSecureCookie,
sameSite: "lax",
path: "/",
});
return res;
}
return async (req: NextRequest) => {
const oldCookie = req.cookies.get(oldCookieName);

// If the old cookie doesn't exist, return the middleware
if (!oldCookie) {
if (req.cookies.get(newCookieName) || !oldCookie || !oldCookie.value) {
// exit early if the new cookie exists or the old cookie doesn't exist or is invalid
return middleware(req);
}

const response = NextResponse.redirect(req.url);
response.cookies.delete(oldCookieName);
try {
const decodedCookie = await decodeLegacyJWT(oldCookie.value);

// If the new cookie exists, delete the old cookie first and rerun middleware
if (req.cookies.get(newCookieName)) {
return response;
}
// If old cookie is invalid, delete the old cookie
if (decodedCookie) {
// Set the new cookie
const encodedCookie = await encode({
token: decodedCookie,
secret: process.env.SECRET_PASSWORD,
salt: newCookieName,
});

const decodedCookie = await decodeLegacyJWT(oldCookie.value);
// Run the middleware with the new cookie set
req.cookies.set({
name: newCookieName,
value: encodedCookie,
});

// If old cookie is invalid, delete the old cookie first and rerun middleware
if (!decodedCookie) {
return response;
}
const res = await runMiddlewareAndDeleteOldCookie(req);

// Set the new cookie
const encodedCookie = await encode({
token: decodedCookie,
secret: process.env.SECRET_PASSWORD,
salt: newCookieName,
});
// Set the new cookie in the response
res.cookies.set(newCookieName, encodedCookie, {
httpOnly: true,
secure: isSecureCookie,
sameSite: "lax",
path: "/",
});

// Set the new cookie with the same value and attributes
response.cookies.set(newCookieName, encodedCookie, {
path: "/",
secure: isSecureCookie,
sameSite: "lax",
httpOnly: true,
});

// Delete the old cookie
response.cookies.delete(oldCookieName);
return res;
}
} catch (e) {
console.error(e);
}

return response;
return runMiddlewareAndDeleteOldCookie(req);
};
}
20 changes: 15 additions & 5 deletions apps/web/src/auth/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import type { NextAuthRequest } from "next-auth";
import NextAuth from "next-auth";

Expand All @@ -7,9 +8,18 @@ import { nextAuthConfig } from "@/next-auth.config";
const { auth } = NextAuth(nextAuthConfig);

export function withAuth(
middleware: (
req: NextAuthRequest,
) => void | Response | Promise<void | Response>,
): (req: NextRequest) => void | Response | Promise<void | Response> {
return (req: NextRequest) => auth(middleware)(req, undefined as never);
middleware: (req: NextAuthRequest) => Promise<NextResponse>,
): (req: NextRequest) => Promise<NextResponse> {
return async (req: NextRequest) => {
const res = await auth(middleware)(req, undefined as never);
if (res) {
return new NextResponse(res.body, {
status: res.status,
headers: res.headers,
url: res.url,
statusText: res.statusText,
});
}
return NextResponse.next();
};
}
4 changes: 3 additions & 1 deletion apps/web/src/utils/session/session-config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { absoluteUrl } from "@rallly/utils/absolute-url";

export const sessionConfig = {
password: process.env.SECRET_PASSWORD ?? "",
cookieName: "rallly-session",
cookieOptions: {
secure: process.env.NEXT_PUBLIC_BASE_URL?.startsWith("https://") ?? false,
secure: absoluteUrl().startsWith("https://") ?? false,
},
ttl: 60 * 60 * 24 * 30, // 30 days
};

0 comments on commit a5a45fe

Please sign in to comment.