DEV Community

Sanjay Arya
Sanjay Arya

Posted on

JWT Authentication in React with react-router

In this blog post, we'll explore the seamless integration of JWT authentication with React and react-router. We'll also learn how to handle public routes, secure authenticated routes, and utilize the axios library to make API requests with the authentication token.

Create a React Project

The following command will create a react project for us.

npm create vite@latest react-auth-demo
Enter fullscreen mode Exit fullscreen mode

We will choose React as our framework and JavaScript as a variant.
To begin working on our project, we need to ensure that all dependencies are properly installed. We can achieve this by running

npm install
Enter fullscreen mode Exit fullscreen mode

within our project directory. Once the installation process is complete, we can launch our React project using the command

npm run dev
Enter fullscreen mode Exit fullscreen mode

Let's take these necessary steps to get our React project up and running smoothly.

Installing Dependencies for React-Router v6 and Axios

Before we proceed, let's ensure we have the necessary dependencies installed for our project. We'll start by installing react-router v6, which will handle routing within our React application. Additionally, we'll install Axios, a powerful library used for making API requests. By following these steps, we'll be equipped with the tools needed to implement seamless routing and perform efficient API communication. Let's begin by installing these dependencies.

npm install react-router-dom axios
Enter fullscreen mode Exit fullscreen mode

Creating the AuthProvider and AuthContext in React
With our project set up and dependencies installed, we're ready to take the next step in implementing JWT authentication. In this section, we'll create an AuthProvider component and an associated AuthContext. These will enable us to store and share authentication-related data and functions throughout our application

In the following code snippet, we'll create the authProvider.js file located at src > provider > authProvider.js. Let's explore the implementation of the AuthProvider and AuthContext.

  1. Import the necessary modules and packages:

    • axios is imported from the "axios" package to handle API requests.
    • createContext, useContext, useEffect, useMemo, and useState are imported from the "react" library.
    import axios from "axios";
    import {
      createContext,
      useContext,
      useEffect,
      useMemo,
      useState,
    } from "react";
    
  2. Create an authentication context using createContext():

    • createContext() creates an empty context object that will be used to share the authentication state and functions between components.
    const AuthContext = createContext();
    
  3. Create the AuthProvider component:

    • This component serves as the provider for the authentication context.
    • It receives children as a prop, which represents the child components that will have access to the authentication context.
    const AuthProvider = ({ children }) => {
      // Component content goes here
    };
    
  4. Define the token state using useState():

    • token represents the authentication token.
    • localStorage.getItem("token") retrieves the token value from the local storage if it exists.
    const [token, setToken_] = useState(localStorage.getItem("token"));
    
  5. Create the setToken function to update the authentication token:

    • This function is used to set the new token value.
    • It updates the token state using setToken_() and stores the token value in the local storage using localStorage.setItem().
    const setToken = (newToken) => {
      setToken_(newToken);
    };
    
  6. Use useEffect() to set the default authorization header in axios and stores the token value in the local storage using localStorage.setItem():

    • This effect runs whenever the token value changes.
    • If the token exists, it sets the authorization header in axios and localStorage.
    • If the token is null or undefined, it removes the authorization header from axios and localStorage.
    useEffect(() => {
      if (token) {
        axios.defaults.headers.common["Authorization"] = "Bearer " + token;
        localStorage.setItem('token',token);
      } else {
        delete axios.defaults.headers.common["Authorization"];
        localStorage.removeItem('token')
      }
    }, [token]);
    
  7. Create the memoized context value using useMemo():

    • The context value includes the token and setToken function.
    • The token value is used as a dependency for memoization.
    const contextValue = useMemo(
      () => ({
        token,
        setToken,
      }),
      [token]
    );
    
  8. Provide the authentication context to the child components:

    • Wrap the children components with the AuthContext.Provider.
    • Pass the contextValue as the value prop of the provider.
    return (
      <AuthContext.Provider value={contextValue}>
        {children}
      </AuthContext.Provider>
    );
    
  9. Export the useAuth hook for accessing the authentication context:

    • useAuth is a custom hook that can be used in components to access the authentication context.
    export const useAuth = () => {
      return useContext(AuthContext);
    };
    
  10. Export the AuthProvider component as the default export:

    • This allows other files to import and use the AuthProvider component as needed.
    export default AuthProvider;
    

Complete Code:

import axios from "axios";
import { createContext, useContext, useEffect, useMemo, useState } from "react";

const AuthContext = createContext();

const AuthProvider = ({ children }) => {
  // State to hold the authentication token
  const [token, setToken_] = useState(localStorage.getItem("token"));

  // Function to set the authentication token
  const setToken = (newToken) => {
    setToken_(newToken);
  };

  useEffect(() => {
    if (token) {
      axios.defaults.headers.common["Authorization"] = "Bearer " + token;
      localStorage.setItem('token',token);
    } else {
      delete axios.defaults.headers.common["Authorization"];
      localStorage.removeItem('token')
    }
  }, [token]);

  // Memoized value of the authentication context
  const contextValue = useMemo(
    () => ({
      token,
      setToken,
    }),
    [token]
  );

  // Provide the authentication context to the children components
  return (
    <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
  );
};

export const useAuth = () => {
  return useContext(AuthContext);
};

export default AuthProvider;
Enter fullscreen mode Exit fullscreen mode

In summary, this code sets up the authentication context using React's context API. It provides the authentication token and the setToken function to child components through the context. It also ensures that the default authorization header in axios is updated with the authentication token whenever it changes.

Creating Routes in React for JWT Authentication

To organize our routes effectively, we'll create a dedicated src > routes folder. Inside this folder, we'll create an index.jsx file, which will serve as the entry point for defining our application's routes. By structuring our routes in a separate folder, we can maintain a clear and manageable routing structure. Let's proceed with creating our routes and explore how we can incorporate JWT authentication into our React application.

Creating a ProtectedRoute Component for Authenticated Routes

In order to secure our authenticated routes and prevent unauthorized access, we'll create a dedicated component called ProtectedRoute. This component will serve as a wrapper for our authenticated routes, ensuring that only authenticated users can access them. By implementing this component, we can easily enforce authentication requirements and provide a seamless user experience. Let's create the ProtectedRoute.jsx file located at src > routes > ProtectedRoute.jsx and enhance the security of our application.

  1. We start by importing the necessary dependencies from the react-router-dom library:

    import { Navigate, Outlet } from "react-router-dom";
    import { useAuth } from "../provider/authProvider";
    
  2. We define the ProtectedRoute component, which will serve as a wrapper for our authenticated routes:

    export const ProtectedRoute = () => {
      const { token } = useAuth();
    
      // Check if the user is authenticated
      if (!token) {
        // If not authenticated, redirect to the login page
        return <Navigate to="/login" />;
      }
    
      // If authenticated, render the child routes
      return <Outlet />;
    };
    
  3. Inside the ProtectedRoute component, we access the token from the useAuth custom hook provided by the AuthContext. This hook allows us to retrieve the authentication token stored in the context.

  4. We then check if the token exists. If the user is not authenticated (token is falsy or null), we use the Navigate component from react-router-dom to redirect the user to the login page ("/login").

  5. If the user is authenticated, we render the child routes using the Outlet component. The Outlet component acts as a placeholder that displays the child components defined in the parent route.

In summary, the ProtectedRoute component serves as a guard for authenticated routes. If the user is not authenticated, they will be redirected to the login page. If the user is authenticated, the child routes defined within the ProtectedRoute component will be rendered using the Outlet component.

This code allows us to easily protect specific routes and control access based on the user's authentication status, providing a secure navigation experience in our React application.

Deep dive into Routes
Now that we have our ProtectedRoute component and authentication context in place, we can proceed with defining our routes. By differentiating between public routes, authenticated routes, and routes for non-authenticated users, we can effectively handle navigation and access control based on JWT authentication. In this code snippet, we'll dive into the src > routes > index.jsx file and explore how we can incorporate JWT authentication into our routing structure. Let's get started!

  1. Import necessary dependencies:

    • RouterProvider and createBrowserRouter are components imported from the react-router-dom library. They are used for configuring and providing the routing functionality.
    • useAuth is a custom hook imported from "../provider/authProvider". It allows us to access the authentication context.
    • ProtectedRoute is a component imported from "./ProtectedRoute". It serves as a wrapper for authenticated routes.
    import { RouterProvider, createBrowserRouter } from "react-router-dom";
    import { useAuth } from "../provider/authProvider";
    import { ProtectedRoute } from "./ProtectedRoute";
    
  2. Define the Routes component:

    • This functional component acts as the entry point for configuring the application routes.
    const Routes = () => {
      const { token } = useAuth();
      // Route configurations go here
    };
    
  3. Access the authentication token using the useAuth hook:

    • The useAuth hook is called to retrieve the token value from the authentication context. It allows us to access the authentication token within the Routes component.
    const { token } = useAuth();
    
  4. Define routes accessible to all users:

    • The routesForPublic array contains route objects that can be accessed by all users. Each route object consists of a path and an element.
    • The path property specifies the URL path for the route, and the element property contains the JSX element/component to render.
    const routesForPublic = [
      {
        path: "/service",
        element: <div>Service Page</div>,
      },
      {
        path: "/about-us",
        element: <div>About Us</div>,
      },
    ];
    
  5. Define routes accessible only to authenticated users:

    • The routesForAuthenticatedOnly array contains route objects that can be accessed only by authenticated users. It includes a protected root route ("/") wrapped in the ProtectedRoute component and additional child routes defined using the children property.
    const routesForAuthenticatedOnly = [
      {
        path: "/",
        element: <ProtectedRoute />,
        children: [
          {
            path: "/",
            element: <div>User Home Page</div>,
          },
          {
            path: "/profile",
            element: <div>User Profile</div>,
          },
          {
            path: "/logout",
            element: <div>Logout</div>,
          },
        ],
      },
    ];
    
  6. Define routes accessible only to non-authenticated users:

    • The routesForNotAuthenticatedOnly array contains route objects that are accessible only to non-authenticated users. It includes a login route ("/login").
    const routesForNotAuthenticatedOnly = [
      {
        path: "/",
        element: <div>Home Page</div>,
      },
      {
        path: "/login",
        element: <div>Login</div>,
      },
    ];
    
  7. Combine and conditionally include routes based on authentication status:

    • The createBrowserRouter function is used to create the router configuration. It takes an array of routes as its argument.
    • The spread operator (...) is used to merge the route arrays into a single array.
    • The conditional expression (!token ? routesForNotAuthenticatedOnly : []) checks if the user is authenticated (token exists). If not, it includes the routesForNotAuthenticatedOnly array; otherwise, it includes an empty array.
    const router = createBrowserRouter([
      ...routesForPublic,
      ...(!token ? routesForNotAuthenticatedOnly : []),
      ...routesForAuthenticatedOnly,
    ]);
    
  8. Provide the router configuration using RouterProvider:

    • The RouterProvider component wraps the router configuration, making it available for the entire application.
    return <RouterProvider router={router} />;
    

Complete Code:

import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";

const Routes = () => {
  const { token } = useAuth();

  // Define public routes accessible to all users
  const routesForPublic = [
    {
      path: "/service",
      element: <div>Service Page</div>,
    },
    {
      path: "/about-us",
      element: <div>About Us</div>,
    },
  ];

  // Define routes accessible only to authenticated users
  const routesForAuthenticatedOnly = [
    {
      path: "/",
      element: <ProtectedRoute />, // Wrap the component in ProtectedRoute
      children: [
        {
          path: "/",
          element: <div>User Home Page</div>,
        },
        {
          path: "/profile",
          element: <div>User Profile</div>,
        },
        {
          path: "/logout",
          element: <div>Logout</div>,
        },
      ],
    },
  ];

  // Define routes accessible only to non-authenticated users
  const routesForNotAuthenticatedOnly = [
    {
      path: "/",
      element: <div>Home Page</div>,
    },
    {
      path: "/login",
      element: <div>Login</div>,
    },
  ];

  // Combine and conditionally include routes based on authentication status
  const router = createBrowserRouter([
    ...routesForPublic,
    ...(!token ? routesForNotAuthenticatedOnly : []),
    ...routesForAuthenticatedOnly,
  ]);

  // Provide the router configuration using RouterProvider
  return <RouterProvider router={router} />;
};

export default Routes;
Enter fullscreen mode Exit fullscreen mode

Final Integration

Now that we have our AuthContext, AuthProvider and Routes ready, let's integrate them together in App.jsx.

  1. Import the necessary components and files:

    • AuthProvider is a component imported from "./provider/authProvider". It provides the authentication context to the application.
    • Routes is a component imported from "./routes". It defines the application routes.
    import AuthProvider from "./provider/authProvider";
    import Routes from "./routes";
    
  2. Wrap the Routes component with the AuthProvider component:

    • The AuthProvider component is used to provide the authentication context to the application. It wraps around the Routes component to make the authentication context available to all components within the Routes component tree.
    return (
      <AuthProvider>
        <Routes />
      </AuthProvider>
    );
    

Complete Code:

import AuthProvider from "./provider/authProvider";
import Routes from "./routes";

function App() {
  return (
    <AuthProvider>
      <Routes />
    </AuthProvider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now that everythings is in place, its time to implement Login and Logout.

Let's create a file for Login Page src > pages > Login.jsx
Login Page

const Login = () => {
  const { setToken } = useAuth();
  const navigate = useNavigate();

  const handleLogin = () => {
    setToken("this is a test token");
    navigate("/", { replace: true });
  };

  setTimeout(() => {
    handleLogin();
  }, 3 * 1000);

  return <>Login Page</>;
};

export default Login;
Enter fullscreen mode Exit fullscreen mode
  • The Login component is a functional component that represents the login page.
  • It imports the setToken function from the authentication context using the useAuth hook.
  • The navigate function from the react-router-dom library is imported to handle navigation.
  • Inside the component, there is a handleLogin function that sets a test token using the setToken function from the context and navigates to the home page ("/") with the replace option set to true.
  • A setTimeout function is used to simulate a delay of 3 seconds before calling the handleLogin function.
  • The component returns the JSX for the login page, which in this case is a placeholder text.

Now we will create a file for Logout Page src > pages > Logout.jsx
Logout Page

import { useNavigate } from "react-router-dom";
import { useAuth } from "../provider/authProvider";

const Logout = () => {
  const { setToken } = useAuth();
  const navigate = useNavigate();

  const handleLogout = () => {
    setToken();
    navigate("/", { replace: true });
  };

  setTimeout(() => {
    handleLogout();
  }, 3 * 1000);

  return <>Logout Page</>;
};

export default Logout;
Enter fullscreen mode Exit fullscreen mode
  • In Logout component, we call setToken function with no arguments, with is equal to setToken(null).

Now, we will replace the existing Login and Logout components in the Routes component with the updated versions.

const routesForNotAuthenticatedOnly = [
  {
    path: "/",
    element: <div>Home Page</div>,
  },
  {
    path: "/login",
    element: <Login />,
  },
];
Enter fullscreen mode Exit fullscreen mode

In the routesForNotAuthenticatedOnly array, the element property of "/login" is set to <Login />, which means that the Login component will be rendered when the user visits the "/login" path.

const routesForAuthenticatedOnly = [
  {
    path: "/",
    element: <ProtectedRoute />, // Wrap the component in ProtectedRoute
    children: [
      {
        path: "/",
        element: <div>User Home Page</div>,
      },
      {
        path: "/profile",
        element: <div>User Profile</div>,
      },
      {
        path: "/logout",
        element: <Logout />,
      },
    ],
  },
];
Enter fullscreen mode Exit fullscreen mode

In the routesForAuthenticatedOnly array, the element property of "/logout" is set to <Logout />, which means that the Logout component will be rendered when the user visits the "/logout" path.

Test Flow

  1. When you first visit the root page /, you will see the "Home Page" from the routesForNotAuthenticatedOnly array.
  2. If you navigate to /login, after a delay of 3 seconds, the login process will be simulated. It will set a test token using the setToken function from the authentication context, and then you will be redirected to the root page / using the navigate function from the react-router-dom library. After the redirect, you will see the "User Home Page" from the routesForAuthenticatedOnly array.
  3. If you then visit /logout, after a delay of 3 seconds, the logout process will be simulated. It will clear the authentication token by calling the setToken function without any argument, and then you will be redirected to the root page / again. Since you are now logged out, we will see the "Home Page" from the routesForNotAuthenticatedOnly array.

This flow demonstrates the login and logout processes, where the user transitions between authenticated and non-authenticated states and the corresponding routes are displayed accordingly.

Source Code

Top comments (30)

Collapse
 
crayoncode profile image
crayoncode

Please keep in mind the implications of storing secrets such as access tokens (e.g. a JWT) in localStorage. Browser storage such as localStorage or sessionStorage do not provide sufficient isolation against e.g. XSS attacks and the secrets kept in localStorage can easily be exposed by malicious code.

Further reading:
auth0.com/blog/secure-browser-stor...
snyk.io/blog/is-localstorage-safe-...

Collapse
 
sanjayttg profile image
Sanjay Arya

Thank you for raising this important concern. You're absolutely right that storing access tokens or any sensitive information in localStorage can pose security risks, especially in the context of XSS attacks. It's crucial to consider these implications and take necessary precautions.

One alternative approach to mitigate such risks is to use techniques like HttpOnly cookies or secure authentication mechanisms. By employing secure storage methods and implementing best practices, we can enhance the overall security of our applications.

I appreciate your valuable input and the reminder to prioritize security in handling sensitive data.

Collapse
 
equiman profile image
Camilo Martinez • Edited

Exellent article, well described and organized, easy to follow.

I didn't understand something on the router. It defines two "/" routes to main, but how it know which one use?
I didn't catch how the router detects when to use the public and the authorized one.

Collapse
 
sanjayttg profile image
Sanjay Arya • Edited

Thank you for your kind words! I'm glad you found the article helpful.

In React Router, the routes are processed in the order they are declared. This means that the first matching route will be rendered.

When a user navigates to a specific route, React Router will check the routes in the order they are defined. If a route's path matches the current URL, React Router will render the corresponding element.

When the user is authenticated (has a token), the routesForNotAuthenticatedOnly is excluded from the route configuration. Therefore, only the routes from routesForPublic and routesForAuthenticatedOnly will be considered. If the path matches "/", React Router will render the <ProtectedRoute /> component, which leads to the "User Home Page" component being displayed.

On the other hand, when the user is not authenticated (no token), the routesForNotAuthenticatedOnly routes are included. In this case, if the path matches "/", React Router will render the "Home Page" component from routesForNotAuthenticatedOnly, because it is declared before the routes from routesForAuthenticatedOnly.

  // Combine and conditionally include routes based on authentication status
  const router = createBrowserRouter([
    ...routesForPublic,
    ...(!token ? routesForNotAuthenticatedOnly : []),
    ...routesForAuthenticatedOnly,
  ]);
Enter fullscreen mode Exit fullscreen mode

The order of route declaration is significant. React Router processes the routes in the order they are defined, and the first matching route is rendered. In this scenario, since the path "/" matches the first route in routesForNotAuthenticatedOnly, that route's corresponding component will be rendered.

So, to clarify, when the user is not authenticated, the "Home Page" component from routesForNotAuthenticatedOnly will be rendered when the path matches "/", as it is declared before the routes from routesForAuthenticatedOnly.

By conditionally including different route configurations based on the authentication status, you can control which routes are available to the user and which components are rendered for different scenarios.

Collapse
 
equiman profile image
Camilo Martinez

That was a Mega explanation!
All clear, thanks!

Collapse
 
kevinosoriocodes profile image
KevinOsorioCodes

Hi, very good article, but I think it is more maintainable if we use useContext with useReducer for these cases, in this way we handle possible predefined state changes and we don't let them change in any way through the code. this way we can have a useAuthStateContext and a useAuthDispatchContext to make it more maintainable and even easier to use. greetings and congratulations!

Collapse
 
sanjayttg profile image
Sanjay Arya

Thank you for your feedback and congratulations! I appreciate your input and agree that using useContext with useReducer can offer better maintainability for handling state changes.

In my projects, I often utilize the combination of useContext and useReducer for managing authentication state. However,
for the purpose of this article, I have chosen to simplify the implementation and use useState.

Here is the updated version of the AuthProvider component that implements the reducer pattern:

import axios from "axios";
import { createContext, useContext, useMemo, useReducer } from "react";

// Create the authentication context
const AuthContext = createContext();

// Define the possible actions for the authReducer
const ACTIONS = {
  setToken: "setToken",
  clearToken: "clearToken",
};

// Reducer function to handle authentication state changes
const authReducer = (state, action) => {
  switch (action.type) {
    case ACTIONS.setToken:
      // Set the authentication token in axios headers and local storage
      axios.defaults.headers.common["Authorization"] = "Bearer " + action.payload;
      localStorage.setItem("token", action.payload);

      // Update the state with the new token
      return { ...state, token: action.payload };

    case ACTIONS.clearToken:
      // Clear the authentication token from axios headers and local storage
      delete axios.defaults.headers.common["Authorization"];
      localStorage.removeItem("token");

      // Update the state by removing the token
      return { ...state, token: null };

    // Handle other actions (if any)

    default:
      console.error(
        `You passed an action.type: ${action.type} which doesn't exist`
      );
  }
};

// Initial state for the authentication context
const initialData = {
  token: localStorage.getItem("token"),
};

// AuthProvider component to provide the authentication context to children
const AuthProvider = ({ children }) => {
  // Use reducer to manage the authentication state
  const [state, dispatch] = useReducer(authReducer, initialData);

  // Function to set the authentication token
  const setToken = (newToken) => {
    // Dispatch the setToken action to update the state
    dispatch({ type: ACTIONS.setToken, payload: newToken });
  };

  // Function to clear the authentication token
  const clearToken = () => {
    // Dispatch the clearToken action to update the state
    dispatch({ type: ACTIONS.clearToken });
  };

  // Memoized value of the authentication context
  const contextValue = useMemo(
    () => ({
      ...state,
      setToken,
      clearToken,
    }),
    [state]
  );

  // Provide the authentication context to the children components
  return (
    <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
  );
};

// Custom hook to easily access the authentication context
export const useAuth = () => {
  return useContext(AuthContext);
};

export default AuthProvider;
Enter fullscreen mode Exit fullscreen mode

And here is the updated version of the Logout component that utilizes the clearToken function from the useAuth hook:

import { useNavigate } from "react-router-dom";
import { useAuth } from "../provider/authProvider";

const Logout = () => {
  const { clearToken } = useAuth();
  const navigate = useNavigate();

  // Function to handle logout
  const handleLogout = () => {
    clearToken(); // Clear the authentication token
    navigate("/", { replace: true }); // Navigate to the home page ("/") with replace option set to true
  };

  // Automatically logout after 3 seconds
  setTimeout(() => {
    handleLogout(); // Invoke the logout action
  }, 3 * 1000);

  return <>Logout Page</>;
};

export default Logout;
Enter fullscreen mode Exit fullscreen mode

Again thank you for sharing your thoughts. I appreciate your support!

Collapse
 
wadigzon profile image
WADIGZON DIAZ-WONG

Good stuff Sanjay, thanks for sharing,
just a little clarification about file name:
authProvider.js is really authProvider.jsx

if you use .js you will get the following/similar error:

The esbuild loader for this file is currently set to "js" but it must be set to "jsx" to be able to parse JSX syntax. You can use "loader: { '.js': 'jsx' }" to do that.

Of course you have the right file name in the source code. :-)

Collapse
 
victor_kasap_739a7ea56b2a profile image
Victor Kasap

Good post, thank you 🙏
What do you think instead of using Context API, using Effector or Zustand?

What if backend sends you an HTTP cookie (JWT), that JWT includes time of creation and time expiration. This is jus first part of the full token, with the second part stored on the server. You only need to check expiration time of the token.

Collapse
 
sanjayttg profile image
Sanjay Arya

Thank you for your kind words! When it comes to alternative state management solutions, libraries like Effector or Zustand can offer different approaches compared to the Context API. While I haven't personally worked with Effector and Zustand, I do plan on exploring them in the future. However, I can share that Redux Toolkit is a widely adopted and powerful state management solution that I intend to cover in an upcoming article.

Regarding JWT tokens with time of creation and expiration, it is a common practice in authentication. If the backend sends the JWT as an HTTP cookie, you can extract relevant information such as the expiration time from the token and store it on the client-side. By checking the expiration time, you can determine the token's validity. If the token has expired, you may need to handle token renewal or reauthentication based on your application's requirements.

Collapse
 
tapansharma profile image
Tapan Sharma

Hi,
Why do we need to create ? Why not simply do the following:
const router = createBrowserRouter([
...routesForPublic,
...(!token ? routesForNotAuthenticatedOnly : []),
...(token ? routesForAuthenticatedOnly : [] ),
]);
We check the token here in createBrowerRouter itself. This is easier and much more maintainable.

Collapse
 
sanjayttg profile image
Sanjay Arya

For the routesForNotAuthenticatedOnly, the logic is as follows:

  • If the user is not authenticated (token is falsy), the routesForNotAuthenticatedOnly array will be included in the routes configuration.
  • If the user is authenticated (token is truthy), an empty array [] will be included instead, effectively excluding the routesForNotAuthenticatedOnly from the routes configuration.
  • This logic is achieved using the conditional operator (!token ? routesForNotAuthenticatedOnly : []).

For the routesForAuthenticatedOnly, the logic is as follows:

  • The routes are always included in the routes configuration.
  • However, when a user visits any of the protected routes (e.g., /, /profile, etc.), the ProtectedRoute component is responsible for checking if the user is authenticated.
  • If the user is not authenticated (token is falsy), they will be redirected to the /login route using the Navigate component from react-router-dom.
  • This redirection ensures that only authenticated users can access the protected routes.
  • The logic for this redirection is implemented within the ProtectedRoute component, where it checks if the user is authenticated and handles the redirection accordingly.

Overall, these mechanisms ensure that the appropriate routes are accessible based on the user's authentication status. If the user is not authenticated, they can access the routesForNotAuthenticatedOnly, and if they are authenticated, they can access the routesForAuthenticatedOnly with the added protection of the ProtectedRoute component redirecting them to the login page if needed.

Collapse
 
eageringdev profile image
Eagering Dev

Great Article.
Newly learned this function.
createBrowserRouter

Collapse
 
reacthunter0324 profile image
React Hunter

Great!
It described in good order and refined explanations.

Collapse
 
pauldumebi profile image
Paul Dumebi

Great Article

Collapse
 
mezieb profile image
Okoro chimezie bright

Thanks for sharing what a great stuff easy to follow.

Collapse
 
bemimg profile image
Bernardo Guerreiro

Wish I could send this to every programmer that had the same struggles that I did, not only did you gave a perfect explanation on handling the token but also routes.
Great job and thank you.

Collapse
 
winglessmachine profile image
winglessmachine

wow , awesome tutorial, straight to the point! just created an account here to give feedback :D

Collapse
 
princemuel profile image
Prince Muel

This article was amazing. I'm using the createBrowserRoutesFromElements function, but it seems I'll change that.
A question I have for you is, how would someone handle refresh tokens using this setup? Thanks

Collapse
 
vidhanshu profile image
vidhanshu borade • Edited

But if we decide whether user is authenticated or not, just on the basis of existence of token, is it okay? won't if user manually add some garbage into the localStorage via inspecting, will let him sign in?
Is there any solution for this?
one solution I think, we can verify the validity of jwt in frontend, ig?

Collapse
 
vehibip22 profile image
vehibip225@glalen.com

how can i handle nested route.
currently at / i have component

and inside i have structure of login page like in Login compo i divided page in two half one half has image and in second half based on route i render either login or sign up . so fo that i have two route set up . at / i have and at at /sign-in i have

with your auth set up this is not working.