Ruddra.com

Maintain Authentication Layer from Redux Middleware Using React Router

Maintain Authentication Layer from Redux Middleware Using React Router
Photo by Erik Witsoe on Unsplash

Maintaining authentication layer in ReactJS can be painful. Back in the old days(even now-a-days), you needed to override every componentDidMount method in class based components to see if the user is authenticated. In the latest ReactJS, there are hooks(specially useEffect) where you need to write these checks. Either way, some codes will be redundant.

No worries, with the help of redux and react router, you can overcome this problem easily using redux middleware. In this article, I am going to share how you can achieve this!!

Prerequisite

You need to use react router and redux, otherwise it won’t work. So probably it is better to avoid this approach in smaller projects where you are not using redux or react router, and it will over-complicate the project.

Connect router to redux

First, you need to connect react router** and redux**. To connect them, you need to use connected react router. Just follow their steps to install and configure the package.

Create a middleware

Once you have connected the router to redux, now it is time to listen to Route Changes. We are going to use a middleware to listen to it. Let us create one:

// authentication middleware
export const authentication = (store) => (next) => (action) => {
  if (action.type === "@@router/LOCATION_CHANGE") {
    const path = action.payload.location.pathname;
    console.log(path);
  }
  return next(action);
};

As you can see here, we are looking for an action type named @@router/LOCATION_CHANGE. This action is fired when a route is changed. From it’s payload, we can grab the path name as well. Later we are going to use that path for certain functions and actions.

Add middleware to redux store

Now, we need to add that middleware to redux store like this:

import { applyMiddleware, createStore, compose } from "redux";

import authentication from "./authenticationMiddleware";

// some code

export const history = createBrowserHistory({
  basename: "baseName",
});
const router = routerMiddleware(history);
const middlewareEnhancer = applyMiddleware(thunkMiddleware, authentication);
const composedEnhancers = compose(middlewareEnhancer, otherMiddlewareEnhancers);

export default function configureStore(preloadedState) {
  return createStore(
    createRootReducer(history),
    preloadedState,
    composedEnhancers
  );
}

Create path to authentication map

We have the path information available in middleware. So we are going to use that information to restrict which path can be accessed with/without authentication. Let us do that:

//authenticationHelper.js
const pathToAuthMap = [
  {
    url: "/",
    isRegex: false,
    requireAuth: true,
  },
  {
    url: "/login",
    isRegex: false,
    requireAuth: false,
  },
  {
    url: /item\/\d+/,
    isRegex: true,
    requireAuth: true,
  },
];

Use ‘pathToAuthMap’ to validate path

In the last step, we described which path requires authentication, and which don’t. Now, we are going to write a function to evaluate if a certain path requires authentication.

//authenticationHelper.js
const checkAuthRequired = (path) => {
  for (const element of pathToAuthMap) {
    if (element.isRegex) {
      if (element.url.test(path)) {
        return element.requireAuth;
      }
    } else if (element.url == path) {
      return element.requireAuth;
    }
  }
  return false; // default value
};

Use ‘checkAuthRequired’ in middleware

Finally, we have everything in our hands, now it is time to connect them together. Means, we are going to use our checkAuthRequired method in middleware.

// authentication middleware
import { push } from "connected-react-router";
import { checkAuthRequired } from "./authenticationHelper";

export const authentication = (store) => (next) => (action) => {
  if (action.type === "@@router/LOCATION_CHANGE") {
    const path = action.payload.location.pathname;
    if (checkAuthRequired(path) && !store.getState().isAuthenticated) {
      return store.dispatch(push("/login"));
    }
  }
  return next(action);
};

Well, the code above is kind of self explanatory. It is checking if a path requires authentication or not, and if yes and user is not authenticated(we are getting that value from a reducer isAuthenticated, assuming we have that reducer from which we can identify if a user is authenticated or not), then we redirect the user to /login page. We are using push function from connected react router.

And that is it, we have a authentication middleware.

Advantages of this approach

  • Cleaner code and keeps the rest of the components simple.
  • Easily maintainable as all the logics for authentication in the same place.
  • No redundant code in components.
  • More reusable components.

Disadvantages of this approach

  • Over engineering.
  • Performance issue might occur as we are checking each and every route change.

In conclusion

Redundancy or over engineering, this topic is up for debate. But I feel this approach offers more pros than cons. I have not tested this code against server side rendering, but works fine for a create-react-app based solution.

Thank you for reading, if you need any help or have questions, please use the comment section below.

Cheers!!

Last updated: Dec 03, 2020


Share Your Thoughts
M↓ Markdown