I am working on an app with Next.js 13 and firebase auth with id tokens.
I want to leverage Next.JS built-in capability for server-side components to fetch user data faster, therefore I need to verify id tokens on the server at initial request. When no user is logged in on protected routes, I want to redirect to login page.
The problem arises when the user was inactive for >1h and the id token has expired. The next request header will send the expired token causing auth.verifyIdToken to reject it. This will redirect the user to login page, before any client-side code had a chance to run, including user.getIdToken to refresh the token.
Is there a way to refresh the id token on server-side? I read here, that there is a work-around using firebase REST API, which seems insecure.
const ServerAuthContextProvider = ({
children,
user,
cookie,
}: {
children: ReactNode;
user: UserRecord;
cookie: Cookie;
}) => {
useEffect(() => {
if (typeof window !== "undefined") {
(window as any).cookie = cookie;
}
return auth.onIdTokenChanged(async (snap) => {
if (!snap) {
cookie.remove("__session");
cookie.set("__session", "", { path: "/" });
return;
}
const token = await snap.getIdToken();
cookie.remove("__session");
cookie.set("__session", token, { path: "/" });
});
}, [cookie]);
return (
<serverAuthContext.Provider
value={{
user,
auth,
}}
>
{children}
</serverAuthContext.Provider>
);
};
);
};
server-side root component
const RootLayout = async ({ children }: { children: React.ReactNode }) => {
const { user } = await verifyAuthToken();
if (!user) redirect("/login");
return (
<html lang="en">
<body>
<ServerAuthContextProvider user={user}>
{children}
</ServerAuthContextProvider>
</body>
</html>
);
};
server-side token verification
const verifyAuthToken = async () => {
const auth = getAuth(firebaseAdmin);
try {
const session = cookies().get("__session");
if (session?.value) {
console.log("found token");
const token = await auth.verifyIdToken(session.value);
const { uid } = token;
console.log("uid found: ", uid);
const user = await auth.getUser(uid);
return {
auth,
user,
};
}
} catch (error: unknown) {
if (typeof error === "string") {
console.log("error", error);
return {
auth,
error,
};
} else if (error instanceof Error) {
console.log("error", error.message);
return {
auth,
error: error.message,
};
}
}
return {
auth,
};
};
For SSR applications, you should use session cookies. Unlike ID Tokens, they do not expire in 1 hour and you can configure them to be active for up to 14 days.
When a user logs in with Firebase client SDK, you can use getIdToken() once and pass to it an API that generates and sets a session cookie that can be read in subsequent requests.
This way you don't have keep the token updated using a listener.
Do make sure you log the user out from the client SDK either by settings auth persistence to none or explicitly logging out the user so in case you clear they cookie (using a logout API) there is no user on client side.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With