In the previous tutorial, we were able to successfully login our user. That was awesome.
Jump To
We know that the main reason for logging in or authenticating a user is to give access to certain restricted features of an application. In view of that, this article will now focus on how to protect routes and how to access such routes. Let's begin by creating two routes. Follow my lead...
Creating the Routes
Jump To
Create Two (2) Components
- Create a new file in the
srcdirectory and name itFreeComponent.js - The file should have the following content:
import React from "react";
export default function FreeComponent() {
return (
<div>
<h1 className="text-center">Free Component</h1>
</div>
);
}
Next, create a another file and name it
AuthComponent.jsThe file should have the following content:
import React from "react";
export default function AuthComponent() {
return (
<div>
<h1 className="text-center">Auth Component</h1>
</div>
);
}
Set up the route
- Install
react-router-dom
npm install --save react-router-dom
Navigate to
index.jsfileImport import
BrowserRouter
import { BrowserRouter } from "react-router-dom";
- wrap the
<App>component with the</BrowserRouter>component. Soindex.jsfile now looks like:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "bootstrap/dist/css/bootstrap.min.css";
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
- Now navigate to the
App.jsfile - Import import
SwitchandRouteat the top of the file
import { Switch, Route } from "react-router-dom";
- Replace the
AccountComponent with the following code
<Switch>
<Route exact path="/" component={Account} />
<Route exact path="/free" component={FreeComponent} />
<Route exact path="/auth" component={AuthComponent} />
</Switch>
You will notice that nothing changed. This is so because the Account component is still our default component when routing. However, we now have access to multiple routes
- Add links for navigation purpose under the
React Authentication Tutorialheading like so:
<Row>
<Col className="text-center">
<h1>React Authentication Tutorial</h1>
<section id="navigation">
<a href="/">Home</a>
<a href="/free">Free Component</a>
<a href="/auth">Auth Component</a>
</section>
</Col>
</Row>
- Navigate to
index.cssto add the following styling for aesthetics purpose
#navigation{
margin-top: 5%;
margin-bottom: 5%;
}
#navigation a{
margin-right: 10%;
}
#navigation a:last-child{
margin-right: 0;
}
Protecting Routes
Jump To:
Having successfully setup routes, we now want to protect one (i.e. the AuthComponent). To do this, we need to create a new component which will help us check if a certain condition has been met before giving allowing a user to access that route.
The condition we will be using in our own case is the token generated during our login. So before we create this ProtectedRoute Component, let's go get the token from the Login component and make it available in all our application.
Get the token
- Install
universal-cookie. This is a cookie package that helps us share a value or variable across our application
npm i universal-cookie -s
Navigate to the
Login.jsfileImport
universal-cookieat the top and initialize it like so:
import Cookies from "universal-cookie";
const cookies = new Cookies();
- Next add the following code in the
thenblock of our axios call
// set the cookie
cookies.set("TOKEN", result.data.token, {
path: "/",
});
In the code above, we are setting cookie with cookie.set(). It takes 3 arguments: Name of the cookie ("TOKEN". it can be any name; just keep it in mind), Value of the cookie (result.data.token) and which page or route we want it to be available (setting the path to "/" makes the cookie available in all the pages). Hopefully, that makes sense
- Below the cookie.set(), add the following line of code to redirect the user to the authComponent after a successful login
// redirect user to the auth page
window.location.href = "/auth";
If you checkout the Login, it should redirect you to the auth page
Create a Component to Protect Routes
Since we have made the token available across the whole application, we now have access to it on all the components or pages already created or yet to be created. Let's continue...
Create a file with a the name:
ProtectedRoutes.jsEnter the following code into the file
import React from "react";
import { Route, Redirect } from "react-router-dom";
import Cookies from "universal-cookie";
const cookies = new Cookies();
// receives component and any other props represented by ...rest
export default function ProtectedRoutes({ component: Component, ...rest }) {
return (
// this route takes other route assigned to it from the App.js and return the same route if condition is met
<Route
{...rest}
render={(props) => {
// get cookie from browser if logged in
const token = cookies.get("TOKEN");
// return route if there is a valid token set in the cookie
if (token) {
return <Component {...props} />;
} else {
// return the user to the landing page if there is no valid token set
return (
<Redirect
to={{
pathname: "/",
state: {
// sets the location a user was about to assess before being redirected to login
from: props.location,
},
}}
/>
);
}
}}
/>
);
}
Hold up! Hold up!! What is actually going on in the
ProtectedRoutescomponent?
First of all, this is more like a template. What actually changes is the condition on which our ProtectedRoutes component is based. In our own case, it is based on the token received from the cookie upon login. So in other application, the condition may be different
Now, this is what is going on here. The ProtectedRoutes component receives a component and then decides if the component should be returned to the user or not. In order to take this decision, it checks if there is a valid token (token is set upon a successful login) coming from the cookie. If the token is undefined, then it redirects to the default path (the landing page in our own case).
The comments in the code will also guide you to understanding what is going on in the component. Follow Patiently...
Using the ProtectedRoutes component
Let's now use the ProtectedRoutes component to guard our Auth Component since we want it to be accessible only to authenticated users.
Navigate to the
App.jsfileImport the
ProtectedRoutescomponent
import ProtectedRoutes from "./ProtectedRoutes";
- Replace
<Route exact path="/auth" component={AuthComponent} />with<ProtectedRoutes path="/auth" component={AuthComponent} />
My
App.jsat this point looks like this:
import { Switch, Route } from "react-router-dom";
import { Container, Col, Row } from "react-bootstrap";
import Account from "./Account";
import FreeComponent from "./FreeComponent";
import AuthComponent from "./AuthComponent";
import ProtectedRoutes from "./ProtectedRoutes";
function App() {
return (
<Container>
<Row>
<Col className="text-center">
<h1>React Authentication Tutorial</h1>
<section id="navigation">
<a href="/">Home</a>
<a href="/free">Free Component</a>
<a href="/auth">Auth Component</a>
</section>
</Col>
</Row>
{/* create routes here */}
<Switch>
<Route exact path="/" component={Account} />
<Route exact path="/free" component={FreeComponent} />
<ProtectedRoutes path="/auth" component={AuthComponent} />
</Switch>
</Container>
);
}
export default App;
Now try to access
http://localhost:3000/authwithout logging in and notice how it redirects you to the landing page. That is amazing. Right?
See Mine Below
Making API calls using useEffect Hook
Jump To:
We have already seen how to make API calls using Axios when we created our Register and Login. I want to use this section to clearly different how to make API call to different kinds of endpoints.
We will make API call to two endpoints that we created in this tutorial using the useEffect hook. The useEffect hook does for react functional component what componentDidMount() does for react class component.
Free Endpoint:
https://nodejs-mongodb-auth-app.herokuapp.com/free-endpointProtected Endpoint:
https://nodejs-mongodb-auth-app.herokuapp.com/auth-endpoint
API call to a free endpoint
- Navigate to the
FreeComponent.jsfile - Import
useEffectanduseStateby Adjusting yourreactimport line with the following
import React, { useEffect, useState, } from "react";
- Next, import axios
import axios from "axios";
- Set an initial state for
messagelike so:
const [message, setMessage] = useState("");
- Just above the
returnstatement, declare theuseEffectfunction like so
useEffect(() => {
}, [])
The empty array (i.e. []) is very important to avoid continuous execution after the API call have been completed
- In the function, set the following configurations
useEffect(() => {
// set configurations for the API call here
const configuration = {
method: "get",
url: "https://nodejs-mongodb-auth-app.herokuapp.com/free-endpoint",
};
}, [])
- Next make the API call using axios like so:
// useEffect automatically executes once the page is fully loaded
useEffect(() => {
// set configurations for the API call here
const configuration = {
method: "get",
url: "https://nodejs-mongodb-auth-app.herokuapp.com/free-endpoint",
};
// make the API call
axios(configuration)
.then((result) => {
// assign the message in our result to the message we initialized above
setMessage(result.data.message);
})
.catch((error) => {
error = new Error();
});
}, [])
setMessage(result.data.message); assigns the message in our result(i.e. result.data.message) to the message we initialized above. Now we can display the message in our component
I have already shown in the last article how to check for the result of our API call in the console. You can do that to trace how we got to result.data.message.
- To display the
messagewe got on ourFreeComponentpage, enter the following code below<h1 className="text-center">Free Component</h1>line
<h3 className="text-center text-danger">{message}</h3>
React will read the message as a variable because of the curly bracket. If the message is without the curly bracket, React reads is as a normal text
This is my
FreeComponent.jsfile at this point:
import React, { useEffect, useState } from "react";
import axios from "axios";
export default function FreeComponent() {
// set an initial state for the message we will receive after the API call
const [message, setMessage] = useState("");
// useEffect automatically executes once the page is fully loaded
useEffect(() => {
// set configurations for the API call here
const configuration = {
method: "get",
url: "https://nodejs-mongodb-auth-app.herokuapp.com/free-endpoint",
};
// make the API call
axios(configuration)
.then((result) => {
// assign the message in our result to the message we initialized above
setMessage(result.data.message);
})
.catch((error) => {
error = new Error();
});
}, []);
return (
<div>
<h1 className="text-center">Free Component</h1>
{/* displaying our message from our API call */}
<h3 className="text-center text-danger">{message}</h3>
</div>
);
}
- My
FreeComponentPage right now:
API call to a protected endpoint
- Navigate to the
AuthComponent.jsfile - Import
useEffectanduseStateby Adjusting yourreactimport line with the following
import React, { useEffect, useState, } from "react";
- Next, import axios
import axios from "axios";
- Import and initialize universal-cookie like so:
import Cookies from "universal-cookie";
const cookies = new Cookies();
- Get the token generated on login like so:
const token = cookies.get("TOKEN");
- Set an initial state for
messagelike so:
const [message, setMessage] = useState("");
- Just above the
returnstatement, declare theuseEffectfunction like so
useEffect(() => {
}, [])
The empty array (i.e. []) is very important to avoid continuous execution after the API call have been completed
- In the function, set the following configurations
useEffect(() => {
// set configurations for the API call here
const configuration = {
method: "get",
url: "https://nodejs-mongodb-auth-app.herokuapp.com/auth-endpoint",
headers: {
Authorization: `Bearer ${token}`,
},
};
}, [])
Notice that this configuration contains a header. That is the main difference from the free-endpoint configuration. This is so because the auth-enpoint is a protected endpoint that is only accessible using an Authorization token as specified in this article. So it is in the header that we specify the Authorization token. Without this header, the API call will return a 403:Forbidden error
- Next, we make the API call like so
// useEffect automatically executes once the page is fully loaded
useEffect(() => {
// set configurations for the API call here
const configuration = {
method: "get",
url: "https://nodejs-mongodb-auth-app.herokuapp.com/auth-endpoint",
headers: {
Authorization: `Bearer ${token}`,
},
};
// make the API call
axios(configuration)
.then((result) => {
// assign the message in our result to the message we initialized above
setMessage(result.data.message);
})
.catch((error) => {
error = new Error();
});
}, []);
- To display the
messagewe got on ourAuthComponentpage, enter the following code below<h1 className="text-center">Auth Component</h1>line
<h3 className="text-center text-danger">{message}</h3>
- My
AuthComponentPage right now:
Logout Function
Finally, we need to logout when we are done viewing our authComponent Page. You know for security reasons. To do this, add a button in the authComponent page.
- Import the Button component like so:
import { Button } from "react-bootstrap";
- Add the following code below the texts
<Button type="submit" variant="danger">Logout</Button>
- We want a logout function to be triggered when the button is clicked so add
onClick={() => logout()}to the button options. So our button will look like this:
{/* logout */}
<Button type="submit" variant="danger" onClick={() => logout()}>
Logout
</Button>
- Now lets create the function. Enter the following code just above the return
// logout
const logout = () => {
}
- Add the following code to the logout function to remove or destroy the token generated during login
// logout
const logout = () => {
// destroy the cookie
cookies.remove("TOKEN", { path: "/" });
}
- Redirect the user to the landing page with the following code
// logout
const logout = () => {
// destroy the cookie
cookies.remove("TOKEN", { path: "/" });
// redirect user to the landing page
window.location.href = "/";
}
- Add
className="text-center"to the parentdivof theAuthComponent. Just to centralize the whole page. You can now remove it from other places. MyAuthComponent.jsfile now has the following content:
import React, { useEffect, useState } from "react";
import { Button } from "react-bootstrap";
import axios from "axios";
import Cookies from "universal-cookie";
const cookies = new Cookies();
// get token generated on login
const token = cookies.get("TOKEN");
export default function AuthComponent() {
// set an initial state for the message we will receive after the API call
const [message, setMessage] = useState("");
// useEffect automatically executes once the page is fully loaded
useEffect(() => {
// set configurations for the API call here
const configuration = {
method: "get",
url: "https://nodejs-mongodb-auth-app.herokuapp.com/auth-endpoint",
headers: {
Authorization: `Bearer ${token}`,
},
};
// make the API call
axios(configuration)
.then((result) => {
// assign the message in our result to the message we initialized above
setMessage(result.data.message);
})
.catch((error) => {
error = new Error();
});
}, []);
// logout
const logout = () => {
// destroy the cookie
cookies.remove("TOKEN", { path: "/" });
// redirect user to the landing page
window.location.href = "/";
}
return (
<div className="text-center">
<h1>Auth Component</h1>
{/* displaying our message from our API call */}
<h3 className="text-danger">{message}</h3>
{/* logout */}
<Button type="submit" variant="danger" onClick={() => logout()}>
Logout
</Button>
</div>
);
}
My working Application is demonstrated below
And that is it for React Authentication!!!
Congratulations! You are now a React Authentication Pro!!!
Conclusion
We began this series from how to Register a user. We then discussed how to Login such a user in the last article. In this article, we have been able to see how to protect route(s) and access them. We have also been able to see how to make API calls depending on the type of endpoint (free or protected). We then concluded by learning how to logout if we are logged in.
That was a lot. No doubt. However, it helped us cover a lot of topics such as hooks, cookie, axios etc. So YES! it was worth it.
All codes are here
Please share to help someone. Thank you for reading
I promise more content coming your way soon




Top comments (11)
Wow... that's a lot of questions.
For sure you can use local storage but you know that isn't a safe path to thread. Anyone can easily access the information
About the comparison, the
protectedRoutescomponent just check if thetokenis defined or not. It does not compare it with the given token. However, even if you manage to bypass theprotectedRoutescomponent to the component itself, it is still useless because it is at that point that the token is compared to the original token provided at the point of login. By the way, the token for each component is gotten directly from the cookie not from the parent component.And YES!!! You can protect as many components as you desire in your project. The
protectedRoutescomponent is like a Higher Order Component (HOC) which super charges other components that is passed through it.we do not use localStorage for checking token instead of that we take the token from the local storage and make a request to backend to check for the verification of token (like is it associated with some user's id in database) but I have a doubt like if I copy my friend's token and store that in my localStorage then I will be able to access the protected route so this might be security issue. Please correct me if I am saying anything wrong and any other strategy to check that. I am confused that in that case how should we verify the token? Thankyou.
Yes that would definitely be an issue. How is this solved, i recently saw JWT-decode am still trying to figure out how to retrive an objectid from mongo, to delete, or edit . How do you achieve that, because on postman you copy the id and paste it there.
I think the flaw issue is now resolved by the refresh token
Hey Samuel, if you are still looking for how to decode the JWT, my article might be of help. Check thies out: dev.to/ebereplenty/decoding-jasonw...
Great explanation. Thanks again. Am i've been stuck for a while trying to protect the routes and everytime i use react router and your tutorial the projects just keeps on loading never ending until i remove all the <switch and Route
I didn't really get you. Hope you have gotten a fix though?
yes i did thanks again
Welcome
Nice post ,
i have a question ,
trying to serve a react website with express ,
build react and serve static files to / endpoint from express.
Can i set / get remove coockie from express codebase?
I m think to remove coockie if Jwt has expired , so to force user redirected to login route from react..