Basic Auth Routing and Redirecting using React Router v6
Tags: react, reactrouter, javascript, beginners
Published: 2023-09-07 16:27:49

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0uql6b216kqz61h9pver.png)

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.](https://react.dev/reference/react/createContext) 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 };
```
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>
);
```

I'm wrapping my `<App />` component in `<UserProvider>` to give it access to the context file and also the [`<BrowserRouter>` component](https://reactrouter.com/en/main/router-components/browser-router) (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
```

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;

```

To test it, let's run `npm start`.

![Home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e56vbzmkt6ntd9qbvj43.png)

![Admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73toupazhh2vadk6tblu.png)

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
```
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
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ub7qhpkfc4lb3faxt8sm.png)

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;

```

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
```
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
```
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
```

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](https://reactrouter.com/en/main)

[Creating Protected Routes](https://medium.com/@dennisivy/creating-protected-routes-with-react-router-v6-2c4bbaf7bc1c)

[Redirect After Login with React Router v6 (video)](https://www.youtube.com/watch?v=2lJuOh4YlGM)