React/Redux server side rendering initial state - reactjs

I have a React/Redux application that keeps track of a user's authenticated state in local storage. The application is also set up to use server side rendering. I'm running into issues when rendering the initial application state. My server creates a new store and emits the SET_INITIAL_STATE action. This initial action on the client side reads localStorage and passes the authenticated information on to my reducers. The server, however, has no knowledge of this logged in state since I'm using stateless JWT located in local storage for authentication.
Since the server and client are out of sync at this point, I'm getting this error:
React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
Which makes sense, because the server is trying to render an unauthenticated state.
What is the accepted standard or practice for setting this initial state that relies solely on something the client has access to?

I found that the OP is right, that cookie storage is the best option. If you're using react, redux, and express for a universal app the option that worked for me was https://github.com/eXon/react-cookie.
Essentially:
In you server side code you can use:
import cookie from 'react-cookie';
function handleRender(req, res) {
cookie.setRawCookie(req.headers.cookie);
// Create new Redux store instance
const store = createStore(rootReducer, {
username: cookie.load('username') || '',
accessLevel: cookie.load('accessLevel') || userRoles.public,
});
//other react/redux/express related code
}
In your react applications, inside the components, you can just save the cookies directly:
import cookie from 'react-cookie';
//first parameter is anything you want, second is the data you want to save
cookie.save( 'username', username );
cookie.save('accessLevel', accessLevel);
Further if you want to store an object, as I did, you can pass in JSON.stringify(your object). cookie.load will automatically parse it for you.
In my code I called cookie.save from the actions, but you can call it directly in components or any abstraction you have.

I found a working solution.
The trick, unfortunately, is to store the authenticated state in a cookie so the session state gets sent to the server automatically upon request.

Related

Is React reduxtoolkit persist on session storage good location to persist user data?

I am building a fullstack MERN app that allows users to login and perform some actions.
Some components are to display or work with user data like email, name, status etc.
Instead of sending a http request to the backend on every component that needs userData, is it a good practice to persist the userData on #reduxtookit persist not on local storage but session storage because if the user closes the browser without logging out, the data is gone on next session, which sounds good to me as logout would do the same.
So instead of sending a http request with loader function on react router v6.4 on every component. I probably should persist the data on login and use useSelector to access the data from every component that needs to work with it.
My question is: is this a good practice?
I persisted the data on reduxtoolkit persist and the pages were a lot more split second quicker than getting the data straight from the backend.

How to implement optional authentication in React/Express app?

I'm building self-hosted bookmarks manager app. I'm using React on frontend and Express on backend. I have already implemented normal authentication system based on JWT. But now I want to make authentication optional. Beacuse it's self-hosted app some users might want to use it only on local network = auth disabled, and other expose it to the Internet = auth enabled.
On backend, I'm using env variable USE_AUTH=false to disable authentication and everything works as it should.
On frontend however, I'm rendering some components and routes based on isAuthenticated value stored in AuthContext state. It is set to true on successful login/sign up and to false on logout.
My question is how can I tell my React frontend that I'm not using auth on backend so it can render all hidden components, don't include token to requests etc.?
The only solution I came up with, is to send request when user opens the app to ask server if it's using authentication. So something like GET /api/auth/ping will return { useAuth: true } or { useAuth: false } depending on USE_AUTH value.
But I don't know if it is as optimal solution. Is there a better option / pattern I can use?
Thanks
I believe the correct way to do this is to specify in your react app, which routes are private and which ones are public, depending on that, components will be rendered.

Is using authReducer with auth token a good practice?

I have been reading various tutorials on how to use, for example, JWT in a React + Redux app.
In those tutorials, JWT is typically saved in local storage and has an expiry date. This is fine.
However, I encounter some examples using an authReducer that reads from the JWT onload and sets the boolean state isAuthed to true or false. This state is used to handle UI changes such as AuthedNavbar or setting private routes.
My concern here is that the authReducer state becomes stale. For example, if the user keeps the app open, the isAuthed state will still be true, even though the token may have expired.
(Of course, the server-side routes will be protected with jwt so the user will not be able to access resources. However, they will still have a bad UX on the front-end.)
Indeed, this contradicts to the principle of "single source of truth." The auth state is set in both redux and local storage.
Not all state has to be handled in redux. We use react local state and handle router state outside of redux.
Therefore, I think that it is better to use only local storage as the source of truth.
What do you think? Hope to get some suggestions!
Don't persist the data isAuthed in the storage system. But only place use the access_token. Then, even if the user changes it will have no problem. Because you'll be checking it through the database system and verify the access_token and know the isAuthed from the database as well.

How to persist redux user login on server (SSR)

I have an application that works as expected on the client with user logins, etc. No problem.
I would like to pre-render some stuff for components which the user can view if they are logged in, however from what I know you can't persist the store on the server. Makes sense as the store is persisted often in localstorage or something on the client. Plus the redux-persist package warns you to conditionally persist only on the client. I'm doing the following when creating the store. This will ensure the client exists and persist if it does. It only persists the user:
if (typeof window !== "undefined") {
persistStore(
store,
{
whitelist: ["user"]
},
() => {
store.dispatch(setPersisted());
}
),
}
Note that I'm using a redux-persist 4.9.1 as I had a hell of a time migrating to v5.x.
So, how do we pre-render things that only a logged-in user can see if the store can't be persisted on the server? Am I going about this totally wrong?

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)?
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:
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.
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.
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.
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.

Resources