Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase auth state persists on client, but not on hard refresh

I'm building an isomorphic React app that uses Express to handle server requests.

When running the bundled React app on the client side, my Firebase login flow works nicely:

  • I login using Firebase's email/password option
  • After authentication, ref.getAuth() successfully returns the user's auth object
  • Subsequent calls to ref.getAuth() while navigating through my app client-side (via react-router) also return a successful auth object.

However, hard refreshes (which would come from the server) don't persist, even after a successful login on the client. Using the same React components in a server context, ref.getAuth() returns null.

Am I missing a step to make this work on the server in the same manner it works on the client (with the use case being a hard-refresh of the site)?

like image 893
Jon Avatar asked Feb 09 '23 07:02

Jon


1 Answers

If you're connecting to Firebase on the server as part of your isomorphic/universal rendering (which I assume you are), Firebase has no way of knowing which user initiated the request to your server that then subsequently issued the request to Firebase—on the client, the user's cookies can be sent along to Firebase, but it's your server, not the client, that's initiating the request on the server, and so is not associated with any given user.

My first thought was, in order to send authentication from the server, you'll need to have some sort of login on your own server; once you verify (with Firebase or otherwise) that the user is who they say they are, you can generate a token that you can save (securely) in the user's session and also send back to the client. Then, on the client, and on each server request, just before rendering your React application with React.render*, you would call authWithCustomToken() with that user's token.

The one caveat, however, is that authentication to a Firebase database is global—when you authenticate a Firebase ref (even in Node.js), every single other ref pointing to the same database gets authenticated with those credentials; you can't log in as different users by using separate refs. So, if your React rendering pipeline on the server does any asynchronous operations between when the auth callback is called and the app is rendered (e.g. if you use something like react-async or do other fancy async data loading before rendering), the user that is authenticated against your Firebase might have changed by the time you go to render your application. If, however, your rendering pipeline is purely synchronous, you should be able to get away with this strategy (getAuth() can help ensure that you have the right auth before you render).

Aside from that, I think the most straightforward solution is the following:

  1. Authenticate your users through your own server, creating a secure token and passing it back to the client for authentication purposes. Store this token in the user's session so the client can request it and auth with it on the client as necessary. You'll also need to generate your own auth data (the stuff that is normally passed to the callback for authWithPassword) and store this in the session as well.

  2. For server requests to your Firebase, use one of the recommended server authentication schemes:

    Using a Firebase app secret: All authentication methods can accept a Firebase app secret instead of a JWT token. This will grant the server complete read and write access to the entire Firebase database. This access will never expire unless it is revoked via the App Dashboard.

    Using a secure JWT with the optional admin claim set to true: This method will grant a server complete read and write access to the entire Firebase database. This token will expire normally, so it is important to set the expiration times accordingly.

    Using a secure JWT designed to give access to only the pieces of data a server needs to touch: This method is more complicated, but it is the safest way to authenticate a server as it lets the Security and Firebase Rules prevent the server from doing anything it's not supposed to, even if it becomes compromised in some way.

  3. Include server logic to ensure that the current logged in user can only access appropriate data. Since the above methods of authentication will grant access to data the user may or may not have access to, you'll need to take your own steps to ensure that users don't get accidental access to things they shouldn't.

  4. Pass the auth data that you stored in the session in step one to the React application as a property, instead of relying on things like ref.getAuth() to get this data inside your React app (since it won't work on the server), to identify the user in your UI.

like image 83
Michelle Tilley Avatar answered Feb 11 '23 21:02

Michelle Tilley