How to convert an Expressjs partial to a React component

Recently I decided to convert some of the partials in my small express app into React components. The partials have worked just fine up to now- my immediate reason to convert them is to simply game some experience working with React.

Eventually, I anticipate having the elements in a React component will enable me to add further interactivity more easily and quickly, so I think there is some long-term benefit as well.

The first partial I decided to convert was the nav. The nav is built with the bootstrap framework and has a few nav items that should only be rendered conditionally, when the user is logged it.

Nav state when the user is logged out
Nav state when the user is logged in

When the nav was in the Express partial, I was passing a variable named isAuthenticated to each view. The variable held the value of req.session:

{{#if isAuthenticated}}
     <li><a href="/auth/sign-out" class="">Sign Out</a></li>
 {{/if}}

But since I was now moving my nav into a react component, which would be rendered by the browser client, I no longer had a way to access req.session.

However, what I did have access to was the cookie that is set in the browser by that same Express middleware:

The cookie ‘connect.sid’ is set in the browser by the express middleware

That meant that I could access the cookie from client-side javascript. To do so, I imported React’s use State and useEffect hooks into nav.jsx:

const {  useState, useEffect } = React;

With those hooks available, I called useState to create a state variable and setter, initially set to false:

const [isLoggedIn, setIsLoggedIn] = useState(false);

Then, I called useEffect from inside of the component, passing an arrow function as it’s first argument and then an empty array as the second. Inside of the arrow function I created a constant that is assigned it’s own arrow function that contains logic which checks for the cookie, while the empty array ensures that logic only runs once on mount:

onst [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const checkCookie = () => {
      const loggedIn = document.cookie.split(';').some(cookie => cookie.trim().startsWith('connect.sid'));
      setIsLoggedIn(loggedIn);
    };

    checkCookie();
  }, []);

I’m using the cookie property of the browser’s document object document.cookie to retrieve any cookies set and check if they match the name I’m looking for, connect.sid

The result of that is passed to the variable loggedIn, which is then passed to the setter, setIsLoggedIn, which then in turn sets the value of isLoggedIn.

In my markup, I wrapped the state-based nav items in a conditional:

            {isLoggedIn ? (
              <li>
                <a className="nav-link" href="/auth/sign-out">Sign Out</a>
              </li>
            ) : (
              <>
              <li>
                <a className="nav-link" href="/auth/sign-in">Sign In</a>
              </li>
              <li>
                <a className="nav-link" href="/auth/sign-up">Sign Up</a>
              </li>
              </>
            )}

The tertiary operator shown here checks if isLoggedIn is true and if so outputs the ‘Sign Out’ nav item. If it is false, it outputs the ‘Sign In’ and ‘Sign Up’ items.

So at this point I was all set, right? Well, not quite. When I tried running the project, and logging in, only the Sign in items appeared, regardless of successful login.

What turned out to be the case is the cookie’s httpOnly flag was set to true. Because of this, my javascript could not successfully retrieve the connect.sid cookie and the value of isLoggedIn remained perpetually false.

To solve this I first tried modifying the middleware code to set httpOnly to false:

app.use(session({
  cookie: { secure: false, httpOnly: false, path: "/", maxAge: 60 * 60 * 1000 }
}));

This worked, however it then occurred to me that this likely was not safe since it now allowed access to the cookie from javascript, and this would open up the cookie to a cross site scripting vulnerability.

A quick bit of google searching easily confirmed that 🫣

In that case, accessing the cookie from the script would not be an option. I backed that code out and updated my app.

Now I felt stuck- I had user sessions implemented, but they only truly lived on the server side, where they were created. The only thing they were sending to the browser was the associated cookie. This was not a problem when I was using only Express views to render my pages, since those views were built on the server and therefore had access to the session object. Now that I was bringing in client-side components in the form of React, scripts called from those components could not access the session.

What I came up with was a method to separate the concerns of user session security and client side conditional data. The tool that allowed me to achieve it was the browser’s localStorage API.

The localStorage API is client side, so I did not have a way to set a value for it from my server code. However, what I was able to do was pass an Express locals variable to all views, containing a boolean value based on the user’s session state. The code was added to my main app file and looked like this:

// expose loggedIn boolean to templates
app.use((req, res, next) => {
  res.locals.loggedIn = !!(req.session && req.session.isLoggedIn);
  next();
});

I did have copilot write the above piece of code. It chose to use a double negation operator to force a true or false (otherwise the absence of the session could potentially return null).

The block of code passes a response variable to the main shared view template that is now accessible from those templates.

I then added the following code to a script tag in my main view to set the localStorage entry:

    // Persist server-side session loggedIn into localStorage for client-side checks
    localStorage.setItem('loggedIn', '{{#if loggedIn}}true{{else}}false{{/if}}');

Finally, I updated my script in my react nav to query that same localStorage item. The script is passed as the callback to my useEffect method:

  const [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const checkStorage = () => {
      const loggedIn = localStorage.getItem('loggedIn') === 'true';
      setIsLoggedIn(loggedIn);
    };

    checkStorage();
  }, []);

And that was all it took!! 😜 This took longer than I expected but was a great learning experience, that has provided me a better understanding of user sessions. Thanks for reading!


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *