Basic Auth Routing and Redirecting using React Router v6

ericksong91

ericksong91

Posted on September 7, 2023

Basic Auth Routing and Redirecting using React Router v6

Introduction

Hi y'all,

This is a quick tutorial on how to utilize some basic authentication routing using React Router v6. This will allow you to protect routes in your application from users who are not logged in.

You can also include route protection against users who do not have certain group permissions as well (that won't be covered today but would be easy to add).

I will also go over how to navigate the user back to the page that they were trying to access before logging in.

Lets get started!

Setting Up Our Project

First start by making a new vanilla React project:

npx create-react-app your-project-name

Move to the directory then type:

npm install react-router-dom

Components and Routing

To continue setup, lets make our sample components.

I'll be creating a components, auth and context folder to house the page components, authentication component and the user component:

File Structure

Now let's go ahead and work on our context file, user.js.

If you're unfamiliar with context, you can read up more about context here. Basically, context allows us to provide a user token and setUser function to all of the components in our application. This isn't limited only to user and setUser; you can pretty much pass any prop you'd like to any component.

import React, { useState } from 'react';

const UserContext = React.createContext();

function UserProvider({ children }) {
    const [user, setUser] = useState(false);

    return (
        <UserContext.Provider value={{ user, setUser }}>
            {children}
        </UserContext.Provider>
    )
};

export { UserContext, UserProvider };
Enter fullscreen mode Exit fullscreen mode

I'll be setting up the user token as a simple state variable so I can turn it on (true) or off (false) with a button. If we had a backend, this is where we would be communicating with the server to verify the user instead.

Make sure to remember to pass the user as well as setUser props so the other components can access them.

Next I'll open up my index.js file in the root of the directory and add these lines:

`index.js`

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';
import { UserProvider } from './context/user';
import './index.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <UserProvider>
    <Router>
    <App />
    </Router>
  </UserProvider>
);
Enter fullscreen mode Exit fullscreen mode

I'm wrapping my <App /> component in <UserProvider> to give it access to the context file and also the <BrowserRouter> component (renamed to <Router>).

For Admin.js and Homepage.js let's give them both a generic template and some buttons for navigation using <Link />.

`./components/Admin.js`

import React from 'react'
import { Link } from 'react-router-dom'

function Admin({ setUser }) {
    return (
      <div>
        <h1>Admin</h1>
        <Link to="/" replace><button>Back to Home</button></Link>
      </div>
    )
}

export default Admin

`./components/Homepage.js`

import React from 'react'
import { Link } from 'react-router-dom'

function Homepage({ setUser }) {
  return (
    <div>
      <h1>Homepage</h1>
      <Link to="/admin" replace><button>Admin Page</button></Link>
    </div>
  )
}

export default Homepage
Enter fullscreen mode Exit fullscreen mode

Now let's make a some routing in the <App /> component and also import the component that we just made.

import React, { useContext } from "react";
import { UserContext } from "./context/user";
import Admin from "./components/Admin";
import Homepage from "./components/Homepage";
import { Routes, Route } from "react-router-dom";

function App() {
  const { user, setUser } = useContext(UserContext);

  return (
   <div className="App">
    <Routes>
     <Route path='/' element={<Homepage />} />
     <Route path='/admin' element={<Admin />} />
     <Route path="login" element={<Login />} />
    </Routes>
   </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

To test it, let's run npm start.

Home

Admin

Great! Now we have some basic routing and a button that lets us navigate to / and /admin! Now that we have all our pages and routes set up, we need to protect them; go into auth.js and setup the authentication!

`./auth/auth.js`

import React from 'react'
import { useLocation, Outlet, Navigate } from 'react-router-dom';

function AuthLayout({ authenticated }) {
    const location = useLocation();
    return authenticated ? <Outlet /> : <Navigate to="/login" 
           replace state={{ from: location }} />;    
};

export default AuthLayout
Enter fullscreen mode Exit fullscreen mode

In auth.js, we're importing useLocation, Outlet and Navigate from react router.

The return statement here is looking to check whether the prop authenticated is a truthy or falsey value. true is if the user is logged in, false if they're not.

<Navigate /> will navigate the user to /login if they're not logged in (user token reads false). We're saving our user's current location to the location variable then saving that state using state={{ from: location }} in <Navigate>.

(If you console.log(location), you can see the state changing every time the url changes; check your console when switching between /admin and /!)

Finally, <Outlet /> will render out the child components of a parent component; in this case, <AuthLayout /> will be our parent component. Every child component under <AuthLayout /> will render normally when the user token reads true.

Before we add <AuthLayout />, we need to add our <Login /> component as well.

./components/Login.js

import React from 'react'
import { useContext } from 'react'
import { UserContext } from '../context/user'

function Login() {
    const { user, setUser } = useContext(UserContext);

    return (
        <div>
            <h1>Login</h1>
            <button onClick={() => setUser(true)}>Login</button>
        </div>
    )
}

export default Login
Enter fullscreen mode Exit fullscreen mode

With this component, the only prop we need is the setUser function using context. With this function, we can use it to login our user every time they click on the Login button.

Login

Now let's go back to App.js and finish the routing. This time we'll import the <AuthLayout /> component and make it the parent component for <Homepage /> and <Admin />. We'll also import <Login /> but keep it unprotected.

./App.js

import React, { useContext } from "react";
import { UserContext } from "./context/user";
import Admin from "./components/Admin";
import Homepage from "./components/Homepage";
import Login from "./components/Login";
import AuthLayout from "./auth/auth";
import { Routes, Route } from "react-router-dom";

function App() {
  const { user, setUser } = useContext(UserContext);

return (
<div className="App">
 <Routes>
  <Route element={<AuthLayout authenticated={user} />}>
    <Route path='/' element={<Homepage />} />
    <Route path='/admin' element={<Admin />} />
  </Route>
  <Route path="login" element={<Login />} />
 </Routes>
</div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

In the <Route> component, we add <AuthLayout /> as the element and pass in the user prop. Since right now our user token reads false, it should automatically redirect us to /login instead.

Since <Login /> is not a child component of <AuthLayout />, /login is accessible even without a truthy user token.

Go ahead and spin up the instance again (or save) to refresh the changes. You should notice immediately that you have been redirected to /login. Try to manually change the url to / and /admin and you'll be redirected to /login every time.

Once you hit the Login button on /login....nothing happens? It's because we haven't added a conditional yet that redirects the user once they're logged in.

Let's go back to our <Login /> component and add that:

./components/Login.js

import React from 'react'
import { useContext } from 'react'
import { UserContext } from '../context/user'
import { useNavigate } from "react-router-dom";

function Login() {
    const { user, setUser } = useContext(UserContext);
    const navigate = useNavigate();

    if(user) {
        navigate('/')
    };

    return (
        <div>
            <h1>Login</h1>
            <button onClick={() => setUser(true)}>Login</button>
        </div>
    );
};

export default Login
Enter fullscreen mode Exit fullscreen mode

We'll add useNavigate to redirect the user if the user token is a truthy value.

Save the changes and now try to login; it should be redirecting you now to / after you log in.

Redirecting the User After Logging In

What if now the user wants the ability to refresh or go directly to a page, login, then keep their place? Normally it would just redirect them to / because of our conditional in <Login />.

We can fix that by adding a conditional that uses the useLocation state that we saved in <AuthLayout />! Remember when console logging location it was saving your browser history to the location state.

Update your <Login /> component:

./components/Login.js 

import React, { useEffect } from 'react'
import { useContext } from 'react'
import { UserContext } from '../context/user'
import { useLocation, useNavigate } from "react-router-dom";

function Login() {
    const { user, setUser } = useContext(UserContext);
    const location = useLocation();
    const navigate = useNavigate();

    if (user && location.state?.from) {
        return navigate(location.state.from)
    };

    return (
        <div>
            <h1>Login</h1>
            <button onClick={() => setUser(true)}>Login</button>
        </div>
    )
}

export default Login
Enter fullscreen mode Exit fullscreen mode

We added a different conditional that checks if user token is true AND location has an available state that we can use to redirect with navigate.

To test this, let's update our <Homepage /> and <Admin /> components to include logout buttons. We'll also update <App /> to pass in the setUser prop but you could also use context instead if you'd like.

./App.js

import React, { useContext } from "react";
import { UserContext } from "./context/user";
import Admin from "./components/Admin";
import Homepage from "./components/Homepage";
import Login from "./components/Login";
import AuthLayout from "./auth/auth";
import { Routes, Route } from "react-router-dom";

function App() {
  const { user, setUser } = useContext(UserContext);

return (
<div className="App">
 <Routes>
  <Route element={<AuthLayout authenticated={user} />}>
    <Route path='/' element={<Homepage {setUser}=setUser />} />
    <Route path='/admin' element={<Admin {setUser}=setUser  />} />
  </Route>
  <Route path="login" element={<Login />} />
 </Routes>
</div>
  );
}

export default App;

./components/Homepage.js

import React from 'react'
import { Link } from 'react-router-dom'

function Homepage({ setUser }) {
  return (
    <div>
      <h1>Homepage</h1>
      <Link to="/admin" replace><button>Admin Page</button></Link>
      <button onClick={() => setUser(false)}>Logout</button>
    </div>
  )
}

export default Homepage

./components/Admin.js

import React from 'react'
import { Link } from 'react-router-dom'

function Admin({ setUser }) {
    return (
      <div>
         <h1>Admin</h1>
         <Link to="/" replace><button>Back to Home</button></Link>
         <button onClick={() => setUser(false)}>Logout</button>
      </div>
    )
}

export default Admin
Enter fullscreen mode Exit fullscreen mode

Save the changes or spin up the instance again and go ahead and login.

After logging in, navigate to /admin using the buttons then logout. Once you're logged out, when you log in again you should notice that you've been returned to the /admin page instead of /!

Conclusion

React Router v6 is a very powerful tool that can be used for authentication and conditional redirects after logging in. This will make your app robust and more user friendly.

Notes

Please let me know in the comments if I've made any errors or if you have any questions! Still new to ReactJS and React Router v6 but I hope this was useful!

Credits

React Router v6 Documentation

Creating Protected Routes

Redirect After Login with React Router v6 (video)

💖 💪 🙅 🚩
ericksong91
ericksong91

Posted on September 7, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related