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
src
directory 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.js
The 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.js
fileImport import
BrowserRouter
import { BrowserRouter } from "react-router-dom";
- wrap the
<App>
component with the</BrowserRouter>
component. Soindex.js
file 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.js
file - Import import
Switch
andRoute
at the top of the file
import { Switch, Route } from "react-router-dom";
- Replace the
Account
Component 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 Tutorial
heading 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.css
to 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.js
fileImport
universal-cookie
at the top and initialize it like so:
import Cookies from "universal-cookie";
const cookies = new Cookies();
- Next add the following code in the
then
block 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.js
Enter 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
ProtectedRoutes
component?
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.js
fileImport the
ProtectedRoutes
component
import ProtectedRoutes from "./ProtectedRoutes";
- Replace
<Route exact path="/auth" component={AuthComponent} />
with<ProtectedRoutes path="/auth" component={AuthComponent} />
My
App.js
at 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/auth
without 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-endpoint
Protected Endpoint:
https://nodejs-mongodb-auth-app.herokuapp.com/auth-endpoint
API call to a free endpoint
- Navigate to the
FreeComponent.js
file - Import
useEffect
anduseState
by Adjusting yourreact
import line with the following
import React, { useEffect, useState, } from "react";
- Next, import axios
import axios from "axios";
- Set an initial state for
message
like so:
const [message, setMessage] = useState("");
- Just above the
return
statement, declare theuseEffect
function 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
message
we got on ourFreeComponent
page, 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.js
file 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
FreeComponent
Page right now:
API call to a protected endpoint
- Navigate to the
AuthComponent.js
file - Import
useEffect
anduseState
by Adjusting yourreact
import 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
message
like so:
const [message, setMessage] = useState("");
- Just above the
return
statement, declare theuseEffect
function 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
message
we got on ourAuthComponent
page, enter the following code below<h1 className="text-center">Auth Component</h1>
line
<h3 className="text-center text-danger">{message}</h3>
- My
AuthComponent
Page 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 parentdiv
of theAuthComponent
. Just to centralize the whole page. You can now remove it from other places. MyAuthComponent.js
file 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
protectedRoutes
component just check if thetoken
is defined or not. It does not compare it with the given token. However, even if you manage to bypass theprotectedRoutes
component 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
protectedRoutes
component 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..