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:
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 };
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 (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
.
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.
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!
Top comments (2)
Love this article ! You save my time
nice