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

setCookie only works sometimes in getServerSideProps #255

Open
NicholasG04 opened this issue Apr 12, 2020 · 25 comments
Open

setCookie only works sometimes in getServerSideProps #255

NicholasG04 opened this issue Apr 12, 2020 · 25 comments
Labels
help wanted Looking for help from community. kind/feature A request for a new feature. kind/question Developer asked a question. No code changes required.

Comments

@NicholasG04
Copy link

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
Copy link
Author

This is happening both if I pass ctx and not.

@maticzav
Copy link
Owner

Hi @NicholasG04.

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

@NicholasG04
Copy link
Author

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...

@vassbence
Copy link
Contributor

vassbence commented Apr 19, 2020

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 :)

@NicholasG04
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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

@NicholasG04
Copy link
Author

NicholasG04 commented May 20, 2020

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

Edit: nope.

@maticzav
Copy link
Owner

@NicholasG04 can't do much without reproduction.

@maticzav maticzav added the help wanted Looking for help from community. label May 25, 2020
@labelsync-manager labelsync-manager bot added kind/question Developer asked a question. No code changes required. kind/feature A request for a new feature. labels May 25, 2020
@mimiqkz
Copy link

mimiqkz commented Jan 31, 2021

@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: {},
    }
  }
}

@KevinKra
Copy link

KevinKra commented Feb 2, 2021

@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.

@mimiqkz
Copy link

mimiqkz commented Feb 2, 2021

@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
Copy link

mimiqkz commented Feb 2, 2021

@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

@KevinKra
Copy link

KevinKra commented Feb 3, 2021

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 },
  };
};

@maticzav
Copy link
Owner

maticzav commented Feb 3, 2021

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.

@kamami
Copy link

kamami commented Feb 4, 2021

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
Copy link

kamami commented Feb 4, 2021

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...

@sjchmiela
Copy link

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.

@goranefbl
Copy link

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.

@AndreasJacobsen
Copy link

AndreasJacobsen commented Jun 6, 2021

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.

@mimiqkz
Copy link

mimiqkz commented Jun 7, 2021

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

@AndreasJacobsen
Copy link

AndreasJacobsen commented Jun 8, 2021

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,
      });
    });

@raje-sh
Copy link

raje-sh commented Nov 28, 2021

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

@novoytalo
Copy link

novoytalo commented Feb 15, 2023

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,
    };
}

@MeTaNoV
Copy link

MeTaNoV commented Apr 13, 2023

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

Thanks, this just saved my life!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Looking for help from community. kind/feature A request for a new feature. kind/question Developer asked a question. No code changes required.
Projects
None yet
Development

No branches or pull requests