DEV Community

EliHood
EliHood

Posted on • Edited on

Why you have to use localStorage for checking for authenticated routes in React.

I spent the day trying to figure out how to avoid using localStorage to stash a user. Ideally, i don't want sensitive information to be displayed on the application tab.

So let me show you why you can't use redux (or even context) to check for authenticated routes.

Keep in mind were going to obviously stash user data in our store or context, but it won't be helpful for determining an authenticated route.

Image description

Image description

As you can see on the first render there is an empty object. What that means is when you pass this object (that probably looks like this if your using redux):

Image description

It will automatically redirect you to a login page, because it sees an empty object on the first renders. Also, it's unlikely that it will recheck the state. Once it redirects to the page, it's practically done doing its conditional rendering.

Basically its saying "hey bro.... user is an empty object... ok, so its not authenticated let me redirect to the login page".

An example of what this logic may look like

import { ReactElement } from 'react';
import { Route, Routes, BrowserRouter, Navigate } from 'react-router-dom';
import Dashboard from './Pages/Dashboard';
import Login from './Pages/Login';

import { getCurrentUser } from './utils';
import Register from './Pages/Register';
import EmailConfirmation from './Pages/EmailConfirmation';
import GoogleRegister from './Pages/GoogleRegister';
import Settings from './Pages/Settings';
import { useSelector } from 'react-redux';
import { authSelector } from './store/authSlice';

const checkAuth = (user: Record<string, any>): boolean => {
  const user = getCurrentUser() || {};
  console.log('first render', user);

  if (Object.keys?.(user).length > 0) return true;
  return false;
};

function AuthPrivateRoute({
  Page,
  isAuthPage = false,
  user,
}: {
  Page: React.ElementType;
  user: Record<string, any>;
  isAuthPage?: boolean;
}): ReactElement {
  /**
   * If user is validated and on an auth page like login or register,
   * redirect them to dashboard so they won't see login/register page.
   */
  if (isAuthPage && checkAuth?.(user)) {
    return <Navigate to={'/dashboard'} replace={true} />;
  }
  if (isAuthPage && !checkAuth?.(user)) {
    return <Page />;
  }

  return checkAuth?.(user) ? <Page /> : <Navigate to={'/login'} replace={true} />;
}

export function AuthProviderRouter() {
  const user = useSelector(authSelector).user;

  console.log('first render in auth router', user);
  return (
    <BrowserRouter>
      <Routes>
        <Route path='/'>
          <Route path={'register'} element={<AuthPrivateRoute user={user} isAuthPage Page={Register} />} />
          <Route path={'login'} element={<AuthPrivateRoute user={user} isAuthPage Page={Login} />} />
          <Route path={'dashboard'} element={<AuthPrivateRoute user={user} Page={Dashboard} />} />
          <Route path={'settings'} element={<AuthPrivateRoute user={user} Page={Settings} />} />
          <Route path={'emailConfirmationSuccess/:userId/:token'} element={<EmailConfirmation />} />
          <Route path={'account/google/processAccount?'} element={<GoogleRegister />} />
          <Route path={'*'} element={<>404, not found</>} />
          <Route path={''} element={<>Welcome to Nobounce!</>} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

Your logic may look a little different than mine.

Basically reading from redux/context state will not be a suffice way to check authentication for providing access to a private route.

So this brings us to localStorage....

As much as localStorage gets alot of flack for security purposes and such, this is probably the only way i've seen that makes sure that the authenticated data is persisted through page refreshes.

Image description

the getCurrentUser code is nothing fancy.

export const getCurrentUser = () => {
  return JSON.parse(localStorage.getItem('user') as any);
};
Enter fullscreen mode Exit fullscreen mode

Image description

First Render YAY.

Thinking about using redux-persist guess what ?

It uses session storage / localStorage under the hood.

Take a look

https://github.com/rt2zz/redux-persist/blob/d8b01a085e3679db43503a3858e8d4759d6f22fa/src/integration/getStoredStateMigrateV4.ts#L26

By the way..

useRef won't save you(saves data between rerenders, not page refreshes) Still won't resolve the issue.

Hopefully this post saves you some time from figuring out why you probably see localStorage used in various react authentication examples. Hopefully you see why its used now.

LocalStorage is the ideal determining factor to check for an authenticate route, localStorage data is persisted and doesn't clear data on refreshes. It will be available on the initial render.

I guess we just have to be mindful of what data we persist. I think ill just store just a token string on localStorage ... just to keep it simple.

Cheers ! 🥳 🥳 🥳

Top comments (0)