nookies icon indicating copy to clipboard operation
nookies copied to clipboard

setCookie only works sometimes in getServerSideProps

Open NicholasG04 opened this issue 4 years ago • 25 comments

Hello,

Am using nookies with setCookie (also tried nookies.set) in getServerSideProps to set a cookie using values that I've checked are ok and I know that the code block is running. However, only sometimes is the cookie actually getting set - it seems to go through phases of working and then not working. Thanks.

NicholasG04 avatar Apr 12 '20 21:04 NicholasG04

This is happening both if I pass ctx and not.

NicholasG04 avatar Apr 12 '20 21:04 NicholasG04

Hi @NicholasG04.

Please attach a reproduction CodeSandbox. I can't do much with an issue like this.

maticzav avatar Apr 14 '20 05:04 maticzav

Yeah sorry about that - I've found a solution. Setting/destroying cookies wasn't working for me serverside/client side but did when I used useEffect(); and so what it seems like is that it needs to be set when the browser has loaded the page. This is annoying for me however as I was doing this for security purposes but oh well...

NicholasG04 avatar Apr 14 '20 18:04 NicholasG04

You should be able to set cookies in getServerSideProps without many problems tho, in getServerSideProps we are talking about a node.js res and res.setHeader('Set-Cookie', ...) in particular. This package is capable of setting cookies on server-side and uses the functions mentioned above, so there must be something on your side.

If you provide a small repro we can help you out :)

vassbence avatar Apr 19 '20 22:04 vassbence

That is certainly an interesting point and I see why it should in fact work. I'll try to reproduce it when I have some time. Thanks for your prompt response.

NicholasG04 avatar Apr 23 '20 14:04 NicholasG04

This seems similar to #249 - using setCookie(ctx, 'userid', AES.encrypt(user.id, process.env.COOKIE_SECRET).toString(), { expires: expirytime }) returns nothing, gives no errors but also doesn't set the cookie. However, doing it in useEffect() works. Not sure why Set-Cookie doesn't seem to be doing anything.

NicholasG04 avatar May 20 '20 15:05 NicholasG04

When playing around now it did it once randomly, but only once. I'm very confused.

NicholasG04 avatar May 20 '20 15:05 NicholasG04

I wonder if the issue might be related to the fact that I immediately use next/router to switch pages?

Edit: nope.

NicholasG04 avatar May 20 '20 16:05 NicholasG04

@NicholasG04 can't do much without reproduction.

maticzav avatar May 21 '20 16:05 maticzav

@maticzav I have the similar problem, but with get nookies.get(ctx). My cookie is still stored, but when I refreshed the page, nookies can't find the cookie again. Odd though, because when I look at Application, the cookie is still there.

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  try {
    const cookies = nookies.get(ctx)
    await admin.auth().verifyIdToken(cookies.token)

    return {
      props: {},
    }
  } catch (err) {
    return {
      redirect: {
        permanent: false,
        destination: '/login',
      },

      props: {},
    }
  }
}

mimiqkz avatar Jan 31 '21 00:01 mimiqkz

@mimiqkz happy/sad to see someone else is having the exact same issue. this guide is what I've been using to try to set up SSR auth through firebase and yeah ... it's been a complete mess. Sometimes the token is there in SSR but other times (most of the time) it's not.

KevinKra avatar Feb 02 '21 22:02 KevinKra

@KevinKra No kidding! I'm doing the same tutorial, have trying to debug 3 days now, not really sure what causes this. Thinking to open a ticket on nextjs as well. When I check the Cookie, I sometimes see it set another cookie with the same name , for no reason ! F.e : name: token. path: / name: token. path: /user

like what?

mimiqkz avatar Feb 02 '21 22:02 mimiqkz

@KevinKra Ok I managed to fix it by taken out his code in context where he uses onIdTokenChanged. Then I just set the cookie on login and destroy when you logout. When you destroy it, declare what path you want to destroy.

However, I don't understand why the package sometimes set cookie 🍪 again with the same name but another path ? It will be nice if the author can help us with this

mimiqkz avatar Feb 02 '21 23:02 mimiqkz

Okay, I've finally made some headway in determining where the issue is and how to fix it.

My solution is based off the tutorial I linked above for setting up SSR auth with cookies and NextJS. I don't know what specifically is causing the issue, but these are the observations and conclusions I've made after troubleshooting this for two days.

So, as of writing this it seems that setting cookies with nookies for some reason doesn't correctly reach the server. I've had many many experiments where I would set the cookie on the client-side, see it in my devtools > application tab, and yet it mysteriously wouldn't be picked up by the ctx.res.headers within the getServerSideProps in NextJS. It would just be undefined.

So, the solution I'm using right now after freshly resolving this issue is to use two packages: js-cookie and nookies. I use js-cookie to set the cookie and then nookies to read the cookie from ctx on server-side. Though, you could probably just drop nookies altogether, but at this moment i'm just happy to finally have things working.


Context (setting, clearing, destroying)

Using the resource I linked above, here are the adjustments I made to the context provider's useEffect to properly save the cookie.

export function AuthProvider({ children }: any) {
  const [user, setUser] = useState<firebase.User | null>(null);

  useEffect(() => {
    if (typeof window !== "undefined") {
      (window as any).nookies = nookies;
    }
    return firebaseAuth.onIdTokenChanged(async (user) => {
      console.log(`token changed!`);
      nookies.destroy(null, "token");

      if (!user) {
        console.log(`no token found...`);
        setUser(null);
        Cookies.set("token", "");
        return;
      }

      console.log(`updating token...`);
      const token = await user.getIdToken();
      Cookies.set("token", token);
      setUser(user);
    });
  }, []);

  // force token refresh every 10 minutes
  useEffect(() => {
    const handle = setInterval(async () => {
      console.log("Interval token refreshing...");
      const user = firebaseAuth.currentUser;
      if (user) await user.getIdToken(true);
    }, 10 * 60 * 1000);

    // clean up
    return () => clearInterval(handle);
  }, []);

  return (
    <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
  );
}

Page SSR (reading)

export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
  const { language, lessonID } = ctx.params;

  try {
    const cookies = nookies.get(ctx);
    const token = await firebaseAdmin.auth().verifyIdToken(cookies.token);

    // * the user is authenticated
   const { uid, email } = token;    
   // ! Fetch stuff here

  } catch (error) {
    console.log("auth error", error);
    return {
      redirect: {
        permanent: false,
        destination: "/auth/login",
      },
      props: {} as never,
    };
  }

  if (!data) {
    return {
      notFound: true,
    };
  }

  return {
    props: { data, params: ctx.params },
  };
};

KevinKra avatar Feb 03 '21 01:02 KevinKra

I think the problem stems from how NextJS handles the new functions. If you use the original getInitialProps function, everything works as expected.

You can see it in the example https://github.com/maticzav/nookies/blob/master/examples/typescript/pages/create.tsx

If someone could open an issue in NextJS repository that would be invaluable. I wanted to do it for some time but haven't found the time yet.

maticzav avatar Feb 03 '21 08:02 maticzav

I can confirm this issue... I am also trying to follow this tutorial mentioned above and I am facing the exact same issue. Already contacted the author on twitter, but did not get any response yet.

kamami avatar Feb 04 '21 13:02 kamami

Okay, I've finally made some headway in determining where the issue is and how to fix it.

My solution is based off the tutorial I linked above for setting up SSR auth with cookies and NextJS. I don't know what specifically is causing the issue, but these are the observations and conclusions I've made after troubleshooting this for two days.

So, as of writing this it seems that setting cookies with nookies for some reason doesn't correctly reach the server. I've had many many experiments where I would set the cookie on the client-side, see it in my devtools > application tab, and yet it mysteriously wouldn't be picked up by the ctx.res.headers within the getServerSideProps in NextJS. It would just be undefined.

So, the solution I'm using right now after freshly resolving this issue is to use two packages: js-cookie and nookies. I use js-cookie to set the cookie and then nookies to read the cookie from ctx on server-side. Though, you could probably just drop nookies altogether, but at this moment i'm just happy to finally have things working.

Context (setting, clearing, destroying)

Using the resource I linked above, here are the adjustments I made to the context provider's useEffect to properly save the cookie.

export function AuthProvider({ children }: any) {
  const [user, setUser] = useState<firebase.User | null>(null);

  useEffect(() => {
    if (typeof window !== "undefined") {
      (window as any).nookies = nookies;
    }
    return firebaseAuth.onIdTokenChanged(async (user) => {
      console.log(`token changed!`);
      nookies.destroy(null, "token");

      if (!user) {
        console.log(`no token found...`);
        setUser(null);
        Cookies.set("token", "");
        return;
      }

      console.log(`updating token...`);
      const token = await user.getIdToken();
      Cookies.set("token", token);
      setUser(user);
    });
  }, []);

  // force token refresh every 10 minutes
  useEffect(() => {
    const handle = setInterval(async () => {
      console.log("Interval token refreshing...");
      const user = firebaseAuth.currentUser;
      if (user) await user.getIdToken(true);
    }, 10 * 60 * 1000);

    // clean up
    return () => clearInterval(handle);
  }, []);

  return (
    <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
  );
}

Page SSR (reading)

export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
  const { language, lessonID } = ctx.params;

  try {
    const cookies = nookies.get(ctx);
    const token = await firebaseAdmin.auth().verifyIdToken(cookies.token);

    // * the user is authenticated
   const { uid, email } = token;    
   // ! Fetch stuff here

  } catch (error) {
    console.log("auth error", error);
    return {
      redirect: {
        permanent: false,
        destination: "/auth/login",
      },
      props: {} as never,
    };
  }

  if (!data) {
    return {
      notFound: true,
    };
  }

  return {
    props: { data, params: ctx.params },
  };
};

This works for me too. But actually, I would like to stick with ncookies only...

kamami avatar Feb 04 '21 13:02 kamami

This may be specific to my use case and infrastructure, but maybe it may help anyone here.

tl;dr: make sure that your cookie value doesn't get URI-encoded without your consent by passing encode: value => value as cookie option. URI-encoding the cookie value is the default behavior of cookie library which this library uses.


  • My server unsets cookies when they are deemed invalid (makes sense, right? why keep them).
  • I was setting the cookie in getServerSideProps successfully, I verified it was being sent to the browser, etc. But then they were nowhere to be seen in the browser settings.
  • Turned out when a JS request to backend was being sent, it cleared the authentication cookies (as they were invalid, URI-encoded).
  • Adding encode: value => value solved the issue.

sjchmiela avatar Feb 19 '21 20:02 sjchmiela

I wonder if the issue might be related to the fact that I immediately use next/router to switch pages?

Edit: nope.

This was an issue for me. When I remove redirection, all works fine. Just debugging.

goranefbl avatar May 28 '21 09:05 goranefbl

I have been struggling with this for days tried the solution from KevinKra by setting cookies using js-cookie but I still get the same error as described here,

Auth.js

import React, { useState, useEffect, useContext, createContext } from "react";
import nookies from "nookies";
import { firebaseClient } from "./firebaseClient";
import Cookies from 'js-cookie'


const AuthContext = createContext<{ user: firebaseClient.User | null }>({
    user: null,
});


export function AuthProvider({ children }: any) {
    const [user, setUser] = useState<firebaseClient.User | null>(null);
    console.log("User is ", user)
    useEffect(() => {
        if (typeof window !== "undefined") {
            (window as any).nookies = nookies;
        }
        return firebaseClient.auth().onIdTokenChanged(async (user) => {
            if (!user) {
                setUser(null);
                Cookies.set("token", "");
                return;
            }

            const token = await user.getIdToken();
            setUser(user);
            Cookies.set("token", token);
        });
    }, []);

    useEffect(() => {
        const handle = setInterval(async () => {
            console.log(`refreshing token...`);
            const user = firebaseClient.auth().currentUser;
            if (user) await user.getIdToken(true);
        }, 10 * 60 * 1000);
        return () => clearInterval(handle);
    }, []);

    return (
        <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
    );
}

export const useAuth = () => {
    return useContext(AuthContext);
};

Authenticated page

export const getServerSideProps = async (req, ctx) => {
  try {
    console.log("CTX er ", ctx)
    const cookies = nookies.get(ctx);
    console.log("Jeg er før cookies, cookies er", cookies.token)
    const token = await firebaseAdmin.auth().verifyIdToken(cookies.token);
    const slug = req.query.slug;

    const revisionQuery = await getClient().fetch(
      `*[_type == "revisionTemplate" && slug.current =='${slug}'][0]`
    );
    return {
      props: {
        data: "Worked!"
      },
    };
  } catch (err) {

    console.log("feil ctx er:", ctx)
    const cookies = nookies.get(ctx);
    console.log("nookies er", cookies)
    console.log("err er:", err)
    return {
      props: {
        data: "Did not worked!"
      },
    };
  }
};

const Revisions = ({ data, ctx }) => {
  const { user } = useAuth();
  console.log("user er", user)
  console.log("Revision data is", ctx)
  return (
    <Layout>
      <Container>
        <p>{`User ID: ${user ? user.uid : 'no user signed in'}`}</p>
        <p>{data}</p>
      </Container>
    </Layout>
  );
};
export default Revisions

Weirdly I never see the user ID in the front end localhost, but some times I see the user ID in the front end on Vercel.

AndreasJacobsen avatar Jun 06 '21 20:06 AndreasJacobsen

@AndreasJacobsen Maybe try to set path in the option. Setting path works fine for me

mimiqkz avatar Jun 07 '21 09:06 mimiqkz

Thank you for the help mimiqkz, but setting path did not resolve the issue. Nor should it really, the default path for js-cookies is / so if no path is set / should be set automatically. Which is what I want to happen

The cookie remains when I open the server rendered page, but the key is deleted every time I refresh the server rendered authenticated page. I find it difficult to understand why it is being deleted, does firebaseAdmin.auth auto-delete cookies somehow?

Edit running the authenticated page without using firebaseAdmin (just trying to return the cookie token back to front end) leads to the same issue

Edit: When using ctx.req.cookies instead of nookies in getServerSideProps I get the data loaded once, but the cookie is still unset in the front end. So data can only be loaded once before I have to log inn again

    const cookies2 = ctx.req.cookies;
    const token = await firebaseAdmin.auth().verifyIdToken(cookies2.token);

    return {
      props: {
        data: token.email,
      },
    };

This is how I set the cookies

      const token = await user.getIdToken();
      setUser(user);
      // Tried both nookies and js-cookies
      // Cookies.set("token", token, { path: "/" });
      nookies.set(null, "token", token, {
        path: "/",
        encode: (v) => v,
      });
    });

AndreasJacobsen avatar Jun 08 '21 11:06 AndreasJacobsen

https://firebase.google.com/docs/hosting/manage-cache#using_cookies

raje-sh avatar Nov 28 '21 13:11 raje-sh

now in 2023 this tuto work... but i have a problem about use it on api route in Nextjs. To solve: i just creat a cons ctx = {res, req} just like this:

export default async function handlerEncryptToken(
    req: NextApiRequest,
    res: NextApiResponse
) {
const ctx = { req, res };
nookies.set(ctx, "token", tokenIdEncrypt, {
            // keys:process.env.,
            maxAge: 30 * 24 * 60 * 60,
            path: "/",
            secure: true,
            // httpOnly: true,
            // sameSite: "strict",
            // secret: process.env.NEXT_PUBLIC_COOKIE_SECRET,
        });

}

using this i can get on serversideprops the cookie

export async function getServerSideProps(ctx: any) {
    const cookies = ctx.req.cookies;

    return {
        props: cookies,
    };
}

novoytalo avatar Feb 15 '23 04:02 novoytalo

https://firebase.google.com/docs/hosting/manage-cache#using_cookies

Thanks, this just saved my life!

MeTaNoV avatar Apr 13 '23 17:04 MeTaNoV