[GH-ISSUE #1903] Problem to setting cookies from backend to frontend nextjs #8968

Closed
opened 2026-04-13 04:13:01 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @yeferson59 on GitHub (Mar 20, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/1903

I am working on a project with a backend built using Express and a frontend built with Next.js. However, I'm experiencing an issue with authentication. When I make requests for login or signup, and then try to set cookies in Next.js followed by a request to get the session, it doesn't work. I've attempted to use the cookie-setting function that I send to the frontend, and I'm also using the nextCookie library function to set cookies received from the server, but I'm still unable to properly retrieve the session.

Originally created by @yeferson59 on GitHub (Mar 20, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/1903 I am working on a project with a backend built using Express and a frontend built with Next.js. However, I'm experiencing an issue with authentication. When I make requests for login or signup, and then try to set cookies in Next.js followed by a request to get the session, it doesn't work. I've attempted to use the cookie-setting function that I send to the frontend, and I'm also using the nextCookie library function to set cookies received from the server, but I'm still unable to properly retrieve the session.
GiteaMirror added the locked label 2026-04-13 04:13:01 -05:00
Author
Owner

@Kinfe123 commented on GitHub (Mar 20, 2025):

may be make sure you have proper cookie configuration, handling CORS correctly if your frontend and backend are using different ports. also feel free to snap your snippet of code for the configs as well

<!-- gh-comment-id:2741391434 --> @Kinfe123 commented on GitHub (Mar 20, 2025): may be make sure you have proper cookie configuration, handling CORS correctly if your frontend and backend are using different ports. also feel free to snap your snippet of code for the configs as well
Author
Owner

@yeferson59 commented on GitHub (Mar 20, 2025):

this is my configuration

// server.ts
this.app = express();
    this.app.use(
      cors({
        origin: [baseUrlFron],
        credentials: true,
      }),
    );
    this.app.use(morgan("dev"));
    this.app.all("/api/auth/*splat", toNodeHandler(auth));

// sign in

export const login = async (req: Request, res: Response) => {
  const { headers, response } = await auth.api.signInEmail({
    returnHeaders: true,
    headers: fromNodeHeaders(req.headers),
    body: {
      email: req.body.email,
      password: req.body.password,
      remember: req.body.rememberMe,
    },
  });
  res.setHeaders(headers);
  res.json(response);
};

// auth -> server action

export const login = async (state: FormStateLogin, formData: FormData) => {
  const { success, error, data } = await loginSchema.safeParseAsync({
    email: formData.get("email"),
    password: formData.get("password"),
    rememberMe: formData.get("rememberMe"),
  });

  const cookie = await cookies();

  if (!success) return { errors: error.flatten().fieldErrors };

  try {
    // Create a new headers object instead of spreading the existing one
    const headersList = await headers();
    const requestHeaders = new Headers();

    // Set Content-Type header
    await setHeaders(headersList, requestHeaders);

    const res = await fetch(`${baseUrl}/api/v1/auth/login`, {
      method: "POST",
      headers: requestHeaders,
      credentials: "include",
      body: JSON.stringify(data),
    });

    if (!res.ok) {
      return {
        message: "Error al iniciar sesión",
      };
    }

    await setCookies(res);

  } catch (error) {
    console.error("Login error:", error);
  }

  cookie.delete("sessionId");
  cookie.delete("cartId");
  revalidatePath("/account");
  redirect("/account");
};

// setHeaders

export const setHeaders = async (
  headersList: ReadonlyHeaders,
  requestHeaders: Headers
) => {
  requestHeaders.set("Content-Type", "application/json");

  
  const authorization = headersList.get("authorization");
  if (authorization) {
    requestHeaders.set("authorization", authorization);
  }

  
  const userAgent = headersList.get("user-agent");
  if (userAgent) requestHeaders.set("user-agent", userAgent);

  
  const ipAddress = await getIpAddress(headersList);
  requestHeaders.set("x-real-ip", ipAddress ?? "");

  return requestHeaders;
};

export const setCookies = async (res: Response) => {
  const setCookies = res.headers?.get("set-cookie");
  if (!setCookies) return;
  const parsed = parseSetCookieHeader(setCookies);
  const cookieHelper = await cookies();
  parsed.forEach((value, key) => {
    if (!key) return;
    const opts = {
      sameSite: value.samesite,
      secure: value.secure,
      maxAge: value["max-age"],
      httpOnly: value.httponly,
      domain: value.domain,
      path: value.path,
    } as const;
    try {
      cookieHelper.set(key, decodeURIComponent(value.value), opts);
    } catch (e) {
      // this will fail if the cookie is being set on server component
    }
  });
  return;
};

export async function getIpAddress(headersList: ReadonlyHeaders) {
  const forwardedFor = headersList.get("x-forwarded-for");
  if (forwardedFor) {
    return forwardedFor.split(",")[0].trim();
  }

  const realIp = headersList.get("x-real-ip");
  if (realIp) {
    return realIp;
  }

  return null;
}
<!-- gh-comment-id:2741576460 --> @yeferson59 commented on GitHub (Mar 20, 2025): this is my configuration ```typescript // server.ts this.app = express(); this.app.use( cors({ origin: [baseUrlFron], credentials: true, }), ); this.app.use(morgan("dev")); this.app.all("/api/auth/*splat", toNodeHandler(auth)); // sign in export const login = async (req: Request, res: Response) => { const { headers, response } = await auth.api.signInEmail({ returnHeaders: true, headers: fromNodeHeaders(req.headers), body: { email: req.body.email, password: req.body.password, remember: req.body.rememberMe, }, }); res.setHeaders(headers); res.json(response); }; // auth -> server action export const login = async (state: FormStateLogin, formData: FormData) => { const { success, error, data } = await loginSchema.safeParseAsync({ email: formData.get("email"), password: formData.get("password"), rememberMe: formData.get("rememberMe"), }); const cookie = await cookies(); if (!success) return { errors: error.flatten().fieldErrors }; try { // Create a new headers object instead of spreading the existing one const headersList = await headers(); const requestHeaders = new Headers(); // Set Content-Type header await setHeaders(headersList, requestHeaders); const res = await fetch(`${baseUrl}/api/v1/auth/login`, { method: "POST", headers: requestHeaders, credentials: "include", body: JSON.stringify(data), }); if (!res.ok) { return { message: "Error al iniciar sesión", }; } await setCookies(res); } catch (error) { console.error("Login error:", error); } cookie.delete("sessionId"); cookie.delete("cartId"); revalidatePath("/account"); redirect("/account"); }; // setHeaders export const setHeaders = async ( headersList: ReadonlyHeaders, requestHeaders: Headers ) => { requestHeaders.set("Content-Type", "application/json"); const authorization = headersList.get("authorization"); if (authorization) { requestHeaders.set("authorization", authorization); } const userAgent = headersList.get("user-agent"); if (userAgent) requestHeaders.set("user-agent", userAgent); const ipAddress = await getIpAddress(headersList); requestHeaders.set("x-real-ip", ipAddress ?? ""); return requestHeaders; }; export const setCookies = async (res: Response) => { const setCookies = res.headers?.get("set-cookie"); if (!setCookies) return; const parsed = parseSetCookieHeader(setCookies); const cookieHelper = await cookies(); parsed.forEach((value, key) => { if (!key) return; const opts = { sameSite: value.samesite, secure: value.secure, maxAge: value["max-age"], httpOnly: value.httponly, domain: value.domain, path: value.path, } as const; try { cookieHelper.set(key, decodeURIComponent(value.value), opts); } catch (e) { // this will fail if the cookie is being set on server component } }); return; }; export async function getIpAddress(headersList: ReadonlyHeaders) { const forwardedFor = headersList.get("x-forwarded-for"); if (forwardedFor) { return forwardedFor.split(",")[0].trim(); } const realIp = headersList.get("x-real-ip"); if (realIp) { return realIp; } return null; } ```
Author
Owner

@Kinfe123 commented on GitHub (Mar 21, 2025):

have you checked the values of the element in the parsed object so that you are passing the right config as well and one thing on cors origin baseUrlFron is this typo is correct as well ?

<!-- gh-comment-id:2742378974 --> @Kinfe123 commented on GitHub (Mar 21, 2025): have you checked the values of the element in the parsed object so that you are passing the right config as well and one thing on cors origin `baseUrlFron` is this typo is correct as well ?
Author
Owner

@yeferson59 commented on GitHub (Mar 21, 2025):

Image
Headers {
  'x-powered-by': 'Express',
  vary: 'Origin',
  'access-control-allow-credentials': 'true',
  'set-cookie': 'better-auth.session_token=SB4sbjJUozqFwvnl2u3QvT67OYjBuwdp.Hp2yf55P4E8msAxPUZrpH3eQpt%2FKy46EW1VyUBEzdec%3D; Max-Age=604800; Path=/; HttpOnly; SameSite=Lax, better-auth.session_data=eyJzZXNzaW9uIjp7InNlc3Npb24iOnsiaXBBZGRyZXNzIjoiMTc5LjE5LjU3LjExNCIsInVzZXJBZ2VudCI6Ik1vemlsbGEvNS4wIChNYWNpbnRvc2g7IEludGVsIE1hYyBPUyBYIDEwXzE1XzcpIEFwcGxlV2ViS2l0LzYwNS4xLjE1IChLSFRNTCwgbGlrZSBHZWNrbykgVmVyc2lvbi8xOC4zIFNhZmFyaS82MDUuMS4xNSIsImV4cGlyZXNBdCI6IjIwMjUtMDMtMjhUMjI6Mzc6MTMuMTgyWiIsInVzZXJJZCI6ImE0Y2QxYTA0LTNmOTYtNGZiMy05MTZmLWM2YzdkYTc2MTkwZCIsInRva2VuIjoiU0I0c2JqSlVvenFGd3ZubDJ1M1F2VDY3T1lqQnV3ZHAiLCJjcmVhdGVkQXQiOiIyMDI1LTAzLTIxVDIyOjM3OjEzLjE4M1oiLCJ1cGRhdGVkQXQiOiIyMDI1LTAzLTIxVDIyOjM3OjEzLjE4M1oifSwidXNlciI6eyJpZCI6ImE0Y2QxYTA0LTNmOTYtNGZiMy05MTZmLWM2YzdkYTc2MTkwZCIsIm5hbWUiOiJZZWZlcnNvbiIsImVtYWlsIjoieWVmZXJzb250b2xvemE1OUBnbWFpbC5jb20iLCJlbWFpbFZlcmlmaWVkIjp0cnVlLCJpbWFnZSI6bnVsbCwiY3JlYXRlZEF0IjoiMjAyNS0wMy0yMVQxNToyMjoxMy43MTJaIiwidXBkYXRlZEF0IjoiMjAyNS0wMy0yMVQxNToyMjoxMy43MTJaIiwicm9sZSI6ImN1c3RvbWVyIiwiYmFubmVkIjpmYWxzZSwiYmFuUmVhc29uIjpudWxsLCJiYW5FeHBpcmVzIjpudWxsLCJpc0FjdGl2ZSI6dHJ1ZSwibGFzdE5hbWUiOiJUb2xvemEiLCJsYXN0TG9naW4iOiIyMDI1LTAzLTIxVDIxOjMwOjU1LjA2MFoifX0sImV4cGlyZXNBdCI6MTc0MjU5NzIzMzIwMSwic2lnbmF0dXJlIjoiRVNrdEUtcFhsNC14TFc2eTAtUlR2T1hDRXp2LTA2elVjTWlyUlU3ZHBiVSJ9; Max-Age=600; Path=/; HttpOnly; SameSite=Lax',
  'content-type': 'application/json; charset=utf-8',
  'content-length': '281',
  etag: 'W/"119-ajPqRQKGBLeIxm8FgEmApGbAnNY"',
  date: 'Fri, 21 Mar 2025 22:37:13 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5'
}

On local, it works but I'm trying on development server on internet and it doesn't work

<!-- gh-comment-id:2744617616 --> @yeferson59 commented on GitHub (Mar 21, 2025): <img width="1137" alt="Image" src="https://github.com/user-attachments/assets/9faef76d-6ee7-4be7-ab7e-44098f1c286f" /> ```typescript Headers { 'x-powered-by': 'Express', vary: 'Origin', 'access-control-allow-credentials': 'true', 'set-cookie': 'better-auth.session_token=SB4sbjJUozqFwvnl2u3QvT67OYjBuwdp.Hp2yf55P4E8msAxPUZrpH3eQpt%2FKy46EW1VyUBEzdec%3D; Max-Age=604800; Path=/; HttpOnly; SameSite=Lax, better-auth.session_data=eyJzZXNzaW9uIjp7InNlc3Npb24iOnsiaXBBZGRyZXNzIjoiMTc5LjE5LjU3LjExNCIsInVzZXJBZ2VudCI6Ik1vemlsbGEvNS4wIChNYWNpbnRvc2g7IEludGVsIE1hYyBPUyBYIDEwXzE1XzcpIEFwcGxlV2ViS2l0LzYwNS4xLjE1IChLSFRNTCwgbGlrZSBHZWNrbykgVmVyc2lvbi8xOC4zIFNhZmFyaS82MDUuMS4xNSIsImV4cGlyZXNBdCI6IjIwMjUtMDMtMjhUMjI6Mzc6MTMuMTgyWiIsInVzZXJJZCI6ImE0Y2QxYTA0LTNmOTYtNGZiMy05MTZmLWM2YzdkYTc2MTkwZCIsInRva2VuIjoiU0I0c2JqSlVvenFGd3ZubDJ1M1F2VDY3T1lqQnV3ZHAiLCJjcmVhdGVkQXQiOiIyMDI1LTAzLTIxVDIyOjM3OjEzLjE4M1oiLCJ1cGRhdGVkQXQiOiIyMDI1LTAzLTIxVDIyOjM3OjEzLjE4M1oifSwidXNlciI6eyJpZCI6ImE0Y2QxYTA0LTNmOTYtNGZiMy05MTZmLWM2YzdkYTc2MTkwZCIsIm5hbWUiOiJZZWZlcnNvbiIsImVtYWlsIjoieWVmZXJzb250b2xvemE1OUBnbWFpbC5jb20iLCJlbWFpbFZlcmlmaWVkIjp0cnVlLCJpbWFnZSI6bnVsbCwiY3JlYXRlZEF0IjoiMjAyNS0wMy0yMVQxNToyMjoxMy43MTJaIiwidXBkYXRlZEF0IjoiMjAyNS0wMy0yMVQxNToyMjoxMy43MTJaIiwicm9sZSI6ImN1c3RvbWVyIiwiYmFubmVkIjpmYWxzZSwiYmFuUmVhc29uIjpudWxsLCJiYW5FeHBpcmVzIjpudWxsLCJpc0FjdGl2ZSI6dHJ1ZSwibGFzdE5hbWUiOiJUb2xvemEiLCJsYXN0TG9naW4iOiIyMDI1LTAzLTIxVDIxOjMwOjU1LjA2MFoifX0sImV4cGlyZXNBdCI6MTc0MjU5NzIzMzIwMSwic2lnbmF0dXJlIjoiRVNrdEUtcFhsNC14TFc2eTAtUlR2T1hDRXp2LTA2elVjTWlyUlU3ZHBiVSJ9; Max-Age=600; Path=/; HttpOnly; SameSite=Lax', 'content-type': 'application/json; charset=utf-8', 'content-length': '281', etag: 'W/"119-ajPqRQKGBLeIxm8FgEmApGbAnNY"', date: 'Fri, 21 Mar 2025 22:37:13 GMT', connection: 'keep-alive', 'keep-alive': 'timeout=5' } ``` On local, it works but I'm trying on development server on internet and it doesn't work
Author
Owner

@yeferson59 commented on GitHub (Mar 21, 2025):

have you checked the values of the element in the parsed object so that you are passing the right config as well and one thing on cors origin baseUrlFron is this typo is correct as well ?

The variable is correct, it contains the server URL, so I don't understand, but it works fine locally.

<!-- gh-comment-id:2744619223 --> @yeferson59 commented on GitHub (Mar 21, 2025): > have you checked the values of the element in the parsed object so that you are passing the right config as well and one thing on cors origin `baseUrlFron` is this typo is correct as well ? The variable is correct, it contains the server URL, so I don't understand, but it works fine locally.
Author
Owner

@yeferson59 commented on GitHub (Mar 22, 2025):

Thanks for your guidance! I finally solved the issue, but the solution was different from what we initially thought. The problem wasn't with the parsing or typos, but with how I was handling cookies between server and client.
I was trying to parse cookies on the server using server actions and then pass them to the client side, but I encountered SSL issues. After fixing the SSL problems, I tried again on the client side but still had no success.
Finally, I switched completely to using Next.js server actions for cookie handling, and that solved everything. It seems there might be a bug or limitation when trying to handle these particular cookies on the client side.
It would be great if there was a dedicated Next.js function or method specifically designed for setting cookies in the server side when they come from an external backend. Having a straightforward server-side method for this specific use case would have saved me a lot of trouble and would be very useful for projects with separate backend services.
Thanks again for pointing me in the right direction!

<!-- gh-comment-id:2744963195 --> @yeferson59 commented on GitHub (Mar 22, 2025): Thanks for your guidance! I finally solved the issue, but the solution was different from what we initially thought. The problem wasn't with the parsing or typos, but with how I was handling cookies between server and client. I was trying to parse cookies on the server using server actions and then pass them to the client side, but I encountered SSL issues. After fixing the SSL problems, I tried again on the client side but still had no success. Finally, I switched completely to using Next.js server actions for cookie handling, and that solved everything. It seems there might be a bug or limitation when trying to handle these particular cookies on the client side. It would be great if there was a dedicated Next.js function or method specifically designed for setting cookies in the server side when they come from an external backend. Having a straightforward server-side method for this specific use case would have saved me a lot of trouble and would be very useful for projects with separate backend services. Thanks again for pointing me in the right direction!
Author
Owner

@Kinfe123 commented on GitHub (Mar 22, 2025):

Glad you fixed it

<!-- gh-comment-id:2745056645 --> @Kinfe123 commented on GitHub (Mar 22, 2025): Glad you fixed it
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#8968