Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Next.js middleware Edge Runtime #887

Open
jonathan-franzen opened this issue Jan 5, 2025 · 2 comments
Open

Add support for Next.js middleware Edge Runtime #887

jonathan-franzen opened this issue Jan 5, 2025 · 2 comments

Comments

@jonathan-franzen
Copy link

jonathan-franzen commented Jan 5, 2025

Cookies are not properly accessed on the immediate Server Component render when set in Middlewares. The middlewares run on the Edge runtime, and although set-cookie headers are properly set, they are not directly available until the next time request headers are parsed. You can read more here: vercel/next.js#65008

The issue this creates for me is that when I refresh my accessToken agains my external backend-api. The updated accessToken saved in the ironSession is not available on the immediate server-component fetch performed afterwards, triggering an additional refresh which is in turn denied due to refreshTokenReuseMitigation.

The solution made by Next.js team is to use a middleware header to merge in the headers to be "available in the same render that invoked middleware".

The header is x-middleware-set-cookie. I have tested this locally, and find a working solution by adding the said header with cookievalue whenever the corresponding x-middleware-next header is present. Like so:

function setCookie(res: ResponseType, cookieValue: string): void {
  if ("headers" in res && typeof res.headers.append === "function") {
    if (res.headers.has("x-middleware-next")) {
      res.headers.set("x-middleware-set-cookie", cookieValue);
    }
    res.headers.append("set-cookie", cookieValue);
    return;
  }
  let existingSetCookie = (res as ServerResponse).getHeader("set-cookie") ?? [];
  if (!Array.isArray(existingSetCookie)) {
    existingSetCookie = [existingSetCookie.toString()];
  }
  (res as ServerResponse).setHeader("set-cookie", [
    ...existingSetCookie,
    cookieValue,
  ]);
}
@jonathan-franzen
Copy link
Author

jonathan-franzen commented Jan 5, 2025

I believe a more generic solution could be to allow additional headers to be set along with the set-cookie header.

save: {
        value: async function save(additionalHeader?: string) {
         ...
         ...
         ...
          setCookie(res, cookieValue);
          if (additionalHeader) {
            setAdditionalCookieHeader(res, cookieValue, additionalHeader);
          }
        },
      },

and

function setAdditionalCookieHeader(
  res: ResponseType,
  cookieValue: string,
  additionalHeader: string,
): void {
  if ("headers" in res && typeof res.headers.append === "function") {
    res.headers.set(additionalHeader, cookieValue);
    return;
  }
}

and then from my middleware in next.js I can just:

await session.save('x-middleware-set-cookie');

@jonathan-franzen
Copy link
Author

A work-around would be to add this to your middleware.ts.

session.me = meData;
await session.save();

// We need to retrieve the iron-session set-cookie, and then set the cookie on the middlewareResponse as well, 
// to make sure it's available on immediate server-component render
const setCookieHeader: string[] = nextResponse.headers.getSetCookie();
		setCookieHeader.map((cookie) => {
			const parsedCookie = parse(cookie);
			const cookieName: string = Object.keys(parsedCookie)[0];
			const cookieValue: string | undefined = parsedCookie[cookieName];
			nextResponse.cookies.set(cookieName, cookieValue || '', {maxAge: parsedCookie['Max-Age'] ? parseInt(parsedCookie['Max-Age']) : 0, path: parsedCookie['Path'] || '/'})
		})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant