nookies
nookies copied to clipboard
setCookie only works sometimes in getServerSideProps
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.
This is happening both if I pass ctx and not.
Hi @NicholasG04.
Please attach a reproduction CodeSandbox. I can't do much with an issue like this.
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...
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 :)
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.
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.
When playing around now it did it once randomly, but only once. I'm very confused.
I wonder if the issue might be related to the fact that I immediately use next/router to switch pages?
Edit: nope.
@NicholasG04 can't do much without reproduction.
@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 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 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?
@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
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 },
};
};
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.
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.
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 thectx.res.headers
within thegetServerSideProps
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
andnookies
. I usejs-cookie
to set the cookie and thennookies
to read the cookie from ctx on server-side. Though, you could probably just dropnookies
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...
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.
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.
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 Maybe try to set path in the option. Setting path works fine for me
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,
});
});
https://firebase.google.com/docs/hosting/manage-cache#using_cookies
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,
};
}
https://firebase.google.com/docs/hosting/manage-cache#using_cookies
Thanks, this just saved my life!