In this article, we would like to introduce you to the most popular and a modern way of protecting or securing your React Typescript Web Application by using JWT Authentication. So, what is this security or protection and why do we need it.
Well, we will discuss all these things in our blog. So, let’s begin:
What is Security and Protection in Software?
In order to secure or save the data and user’s information from an unknown third-party library, XSS attacks, web scraping or a professional hacker, you can use certain security protocols like HTTPS over HTTP or making authentication in your application or maybe you can use any other security encryption methods or checksum methods so that your application’s data and integrity remains safe.
Why do we need security?
Well, in order to protect your applications from the certain attacks and hackers as we have mentioned above some reasons why we need it. Let me explain you the same with an example:
Example 1:
If you plan to go to a hill station for a picnic with a whole family, then why do you keep the doors and windows locked when you leave your house?
Because someone can enter your house if your doors and windows are not locked right? Thieves can take precious items; any animal can rehabilitate like a snake. And you can’t do anything because what’s gone is gone, because you didn’t have the security. Right? Lock protects your house the same way as the security does in your software applications.
Almost every company nowadays uses security or authentication like JWT (JSON Web Token). If JWT is not implemented, a large number of user’s accounts can be hacked by any hacker.
What is JWT or JSON Web Token?
JSON Web Token is a modern Internet level standard for generating the data with a voluntary signature and voluntary encryption whose payload carries JSON that declares some number of claims. The tokens have been signed either by using a private secret or a public/private key.
Also, read: Interfaces in Typescript: What are they and how to use them?
How to implement JWT into React TypeScript Application (Step by step process)
Steps 1: Create a React TypeScript App on your system by following command:
npx create-react-app <Any-name-you want> --template typescript
Steps 2:
Goto your React Typescript Application Directory and install a bootstrap, axios, react-hook-form, react-toastify & react-router-dom package by following command:
npm install axios bootstrap [email protected] react-hook-form react-toastify
Steps 3:
In your App.tsx file, clear all the code and paste the following. Basically, what are we are trying to do here is to import all the component we will create in future and adding react-router-dom functionality for routing into the pages with a different-different links:
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.bundle.min.js";
import SignUp from "./Components/SignUp";
import Login from "./Components/Login";
import PrivateRoute from "./Auth/PrivateRoute";
import Home from "./Components/Home";
import {BrowserRouter, Route, Switch} from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute exact path="/login" component={Login}/>
<Route exact path="/register" component={SignUp} />
</Switch>
</BrowserRouter>
);
}
export default App;
Steps 4:
Now we will create a private route for our application. Create a folder named “Auth” and under this folder create a new file known as a PrivateRoute.tsx file, copy all the code and paste the following, basically what we are trying to do here is we are taking out our JWT Token from the LocalStorage.
And check if we do not have a JWT token in our LocalStorage then we will go to the Login route, else if it has a token then it will remain on the homepage.
import React from "react";
import { Redirect, Route } from "react-router-dom";
const PrivateRoute = (props) => {
const token = localStorage.getItem("auth");
return <>{token ? <Route {...props} /> : <Redirect to="/login" />}</>;
};
export default PrivateRoute;
Steps 5:
Now we will create a UI for this. First go to the src folder, then inside the src folder make a new folder named as “Components”.
And inside the components folder make 3 files as:
- Login.tsx
- SignUp.tsx
- Home.tsx
- home.css
Before we proceed further make sure that you have your own Node API of JWT Authentication which provides JWT Token. Because we are using the one created at our local machine. Please change the http://localhost:4000 URL in code according to your API URL code.
In Login.tsx
import React, { FC } from "react";
import { Link } from "react-router-dom";
import { useForm } from "react-hook-form";
import axios from "axios";
import { ToastContainer, toast, Flip } from "react-toastify";
import "react-toastify/dist/ReactToastify.min.css";
import { RouteComponentProps } from "react-router";
type SomeComponentProps = RouteComponentProps;
const Login: FC<SomeComponentProps> = ({ history }): JSX.Element => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const login = (data: any) => {
let params = {
email: data.email,
password: data.password,
};
axios
.post("http://localhost:4000/api/login", params)
.then(function (response) {
// IF EMAIL ALREADY EXISTS
if (response.data.success === false) {
toast.error(response.data.error, {
position: "top-right",
autoClose: 3000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: false,
progress: 0,
toastId: "my_toast",
});
} else {
toast.success(response.data.message, {
position: "top-right",
autoClose: 3000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: false,
progress: 0,
toastId: "my_toast",
});
localStorage.setItem("auth", response.data.token);
setTimeout(() => {
history.push("/");
}, 3000);
}
})
.catch(function (error) {
console.log(error);
});
};
return (
<>
<div className="container">
<div
className="row d-flex justify-content-center align-items-center"
style={{ height: "100vh" }}
>
<div className="card mb-3" style={{ maxWidth: "320px" }}>
<div className="col-md-12">
<div className="card-body">
<h3 className="card-title text-center text-secondary mt-3">
Login Form
</h3>
<form autoComplete="off" onSubmit={handleSubmit(login)}>
<div className="mb-3 mt-4">
<label className="form-label">Email</label>
<input
type="email"
className="form-control shadow-none"
id="exampleFormControlInput1"
{...register("email", { required: "Email is required!" })}
/>
{errors.email && (
<p className="text-danger" style={{ fontSize: 14 }}>
{errors.email.message}
</p>
)}
</div>
<div className="mb-3">
<label className="form-label">Password</label>
<input
type="password"
className="form-control shadow-none"
id="exampleFormControlInput2"
{...register("password", {
required: "Password is required!",
})}
/>
{errors.password && (
<p className="text-danger" style={{ fontSize: 14 }}>
{errors.password.message}
</p>
)}
</div>
<div className="text-center mt-4 ">
<button
className="btn btn-outline-primary text-center shadow-none mb-3"
type="submit"
>
Submit
</button>
<p className="card-text pb-2">
Have an Account?{" "}
<Link style={{ textDecoration: "none" }} to={"/register"}>
Sign Up
</Link>
</p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar
closeOnClick
rtl={false}
pauseOnFocusLoss={false}
draggable={false}
pauseOnHover
limit={1}
transition={Flip}
/>
</>
);
};
export default Login;
In SignUp.tsx
import React, { FC } from "react";
import { Link, RouteComponentProps } from "react-router-dom";
import { useForm } from "react-hook-form";
import axios from "axios";
import { ToastContainer, toast, Flip } from "react-toastify";
import "react-toastify/dist/ReactToastify.min.css";
type SomeComponentProps = RouteComponentProps;
const SignUp: FC<SomeComponentProps> = ({ history }) => {
const {
register,
handleSubmit,
watch,
reset,
formState: { errors },
} = useForm();
const submitData = (data: any) => {
let params = {
firstname: data.firstname,
lastname: data.lastname,
email: data.email,
password: data.password,
confirmpassword: data.cpassword,
};
console.log(data);
axios
.post("http://localhost:4000/api/signup", params)
.then(function (response) {
toast.success(response.data.message, {
position: "top-right",
autoClose: 3000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: false,
progress: 0,
toastId: "my_toast",
});
reset();
setTimeout(() => {
history.push("/login");
}, 3000);
})
.catch(function (error) {
console.log(error);
});
};
return (
<>
<div className="container">
<div
className="row d-flex justify-content-center align-items-center"
style={{ height: "100vh" }}
>
<div className="card mb-3 mt-3 rounded" style={{ maxWidth: "500px" }}>
<div className="col-md-12">
<div className="card-body">
<h3 className="card-title text-center text-secondary mt-3 mb-3">
Sign Up Form
</h3>
<form
className="row"
autoComplete="off"
onSubmit={handleSubmit(submitData)}
>
<div className="col-md-6">
<div className="">
<label className="form-label">Firstname</label>
<input
type="text"
className="form-control form-control-sm"
id="exampleFormControlInput1"
{...register("firstname", {
required: "Firstname is required!",
})}
/>
{errors.firstname && (
<p className="text-danger" style={{ fontSize: 14 }}>
{errors.firstname.message}
</p>
)}
</div>
</div>
<div className="col-md-6">
<div className="">
<label className="form-label">Lastname</label>
<input
type="text"
className="form-control form-control-sm"
id="exampleFormControlInput2"
{...register("lastname", {
required: "Lastname is required!",
})}
/>
{errors.lastname && (
<p className="text-danger" style={{ fontSize: 14 }}>
{errors.lastname.message}
</p>
)}
</div>
</div>
<div className="">
<label className="form-label">Email</label>
<input
type="email"
className="form-control form-control-sm"
id="exampleFormControlInput3"
{...register("email", { required: "Email is required!" })}
/>
{errors.email && (
<p className="text-danger" style={{ fontSize: 14 }}>
{errors.email.message}
</p>
)}
</div>
<div className="">
<label className="form-label">Password</label>
<input
type="password"
className="form-control form-control-sm"
id="exampleFormControlInput5"
{...register("password", {
required: "Password is required!",
})}
/>
{errors.password && (
<p className="text-danger" style={{ fontSize: 14 }}>
{errors.password.message}
</p>
)}
</div>
<div>
<label className="form-label">Confirm Password</label>
<input
type="password"
className="form-control form-control-sm"
id="exampleFormControlInput6"
{...register("cpassword", {
required: "Confirm Password is required",
validate: (value) =>
value === watch("password") ||
"Passwords don't match.",
})}
/>
{errors.cpassword && (
<p className="text-danger" style={{ fontSize: 14 }}>
{errors.cpassword.message}
</p>
)}
</div>
<div className="text-center mt-4 ">
<button
className="btn btn-outline-primary text-center shadow-none mb-3"
type="submit"
>
Submit
</button>
<p className="card-text">
Already have an account?{" "}
<Link style={{ textDecoration: "none" }} to={"/login"}>
Log In
</Link>
</p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar
closeOnClick
rtl={false}
pauseOnFocusLoss={false}
draggable={false}
pauseOnHover
limit={1}
transition={Flip}
/>
</>
);
};
export default SignUp;
In Home.tsx
import React, { FC } from "react";
import { RouteComponentProps } from "react-router-dom";
import "./home.css";
type SomeComponentProps = RouteComponentProps;
const Home: FC<SomeComponentProps> = ({ history }) => {
const logout = () => {
localStorage.clear();
history.push("/login");
};
return (
<>
<div
style={{
display: "flex",
justifyContent: "space-between",
paddingLeft: 50,
paddingRight: 50,
}}
>
<div>
<h3 className="m-3">Home</h3>
</div>
<div>
<button type="submit" className="buttons" onClick={logout}>
Logout
</button>
</div>
</div>
<div className="container">
<div
className="row d-flex justify-content-center align-items-center text-center"
style={{ height: "100vh" }}
>
<p className="muted display-6">Hello User👋</p>
</div>
</div>
</>
);
};
export default Home;
In home.css
.buttons
{
color: white;
background: cornflowerblue;
padding: 10px 40px;
margin: 10px;
border: none;
border-radius: 30px;
cursor: pointer;
}
Also, read: How to call web API with useEffect hook in React TypeScript?
Steps 6:
Now execute the code by following command:
npm start
Steps 7:
Now you will see your output like this:
Signup Page:
Login Page:
After, entering a valid login detail, inspect the browser if there is any JWT token or not? If it is a valid login then the screen will look like this, else it will show you an incorrect password message from your API.
Now, if you notice there is a weird auth token generated on Application ‘Tab’ Under LocalStorage auth area. This is signed or a private key which is required by JWT Authentication from an API that you have used.
In case you delete this token from the browser you will be again redirected to the Login page as there is no token available on it and the private route will play its part here.
Short Snippet of Logic:
- Login: In Login, you are just passing the json data of a user and getting a token from a RESTful API.
- Signup: In Sign Up, you are again passing the json data of a user and saving the user into the database.
- Home: In Home, you are logging in our user with the correct credential that he/she enters and set our JWT Token to the browser’s LocalStorage which is coming from Login API.
- Logout: In Logout, you clear the JWT Token from your LocalStorage.
Also, read: Top Web Development Challenges and Solutions
Conclusion:
JWT Authentications are widely used nowadays, kindly use it only where it is required in your projects. Do not use them if you are just using signup login for your personal use. Because your data is local and it will be only available to you, so there is no point in using it for personal use. But yes, it’s a very powerful, robust and secure method to transmit your data.
Code Availability:
https://github.com/Akshay80/React-TS-login-using-jwt/tree/main/src/Auth
Thank you so much for reading this blog so patiently 😊,
Have a good day 👋.
Web Development Services
Are you looking for a reliable web development company? Our highly skilled web developers enables us to deliver result oriented web development services. Contact our team to understand, how we can help you in achieving your business goals.