Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

refresh firebase id token server-side

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.

Context

I use the `firebaseui` [package][2] for login, which creates the initial id token & refresh token. Then I have an `AuthContextProvider` to provide & refresh the id token on the client:
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,
  };
};
like image 927
flair Avatar asked Sep 08 '26 21:09

flair


1 Answers

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.

like image 116
Dharmaraj Avatar answered Sep 11 '26 18:09

Dharmaraj



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!