Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reconcile Firebase Auth token refreshing with Server-Side Rendering

We're using Firebase in a Next.js app at work. I'm new to both, but did my best to read up on both. My problem is more with Firebase, not so much with Next.js. Here's the context:

  • In the client app, I make some calls to our API, passing a JWT (the ID token) in an Authorization header. The API calls admin.auth().verifyIdToken to check that the ID token is fresh enough. This works fine, since I am more or less guaranteed that the ID token gets refreshed regularly (through the use of onIDTokenChanged (doc link)

  • Now I want to be able to Server-Side Render my app pages. In order to do that, I store the ID token in a cookie readable by the server. But from here on, I have no guarantee that the ID token will be fresh enough next time the user loads the app through a full page load.

I cannot find a server-side equivalent of onIDTokenChanged.

This blog post mentions a google API endpoint to refresh a token. I could hit it from the server and give it a refresh token, but it feels like I'm stepping out of the Firebase realm completely and I'm worried maintaining an ad-hoc system will be a burden.

So my question is, how do people usually reconcile Firebase auth with SSR? Am I missing something?

Thank you!

like image 997
Mikael Gramont Avatar asked Oct 31 '25 11:10

Mikael Gramont


1 Answers

I've had that same problem recently, and I solved by handling it myself. I created a very simple page responsible for forcing firebase token refresh, and redirecting user back to the requested page. It's something like this:

  • On the server-side, check for token exp value after extracting it from cookies (If you're using firebase-admin on that server, it will probably tell you as an error after verifying it)
// Could be a handler like this
const handleTokenCookie = (context) => {
  try {
    const token = parseTokenFromCookie(context.req.headers.cookie)
    await verifyToken(token)
  } catch (err) {
    if (err.name === 'TokenExpired') {
      // If expired, user will be redirected to /refresh page, which will force a client-side
      // token refresh, and then redirect user back to the desired page
      const encodedPath = encodeURIComponent(context.req.url)
      context.res.writeHead(302, {
        // Note that encoding avoids URI problems, and `req.url` will also
        // keep any query params intact
        Location: `/refresh?redirect=${encodedPath}`
      })
      context.res.end()
    } else {
      // Other authorization errors...
    }
  }
}

This handler can be used on the /pages, like this

// /pages/any-page.js
export async function getServerSideProps (context) {
  const token = await handleTokenCookie(context)
  if (!token) {
    // Token is invalid! User is being redirected to /refresh page
    return {}
  }

  // Your code...
}
  • Now you need to create a simple /refresh page, responsible for forcing firebase token refresh on client-side, and after both token and cookie are updated, it should redirect user back to the desired page.
// /pages/refresh.js

const Refresh = () => {
  // This hook is something like https://github.com/vercel/next.js/blob/canary/examples/with-firebase-authentication/utils/auth/useUser.js
  const { user } = useUser()
  React.useEffect(function forceTokenRefresh () {
    // You should also handle the case where currentUser is still being loaded

    currentUser
      .getIdToken(true) // true will force token refresh
      .then(() => {
        // Updates user cookie
        setUserCookie(currentUser)

        // Redirect back to where it was
        const decodedPath = window.decodeURIComponent(Router.query.redirect)
        Router.replace(decodedPath)
      })
      .catch(() => {
        // If any error happens on refresh, redirect to home
        Router.replace('/')
      })
  }, [currentUser])

  return (
    // Show a simple loading while refreshing token?
    <LoadingComponent />
  )
}

export default Refresh

Of course it will delay the user's first request if the token is expired, but it ensures a valid token without forcing user to login again.

like image 110
cchiaramelli Avatar answered Nov 02 '25 14:11

cchiaramelli



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!