Welcome folks today in this blog post we will be building the jwt
authentication system in react.js & node.js and express using mongodb
database in browser using javascript. All the full source code of the application is shown below.
Get Started
In order to get started you need to make a new node.js
project using the below command as shown below
npm init -y
And now after that we need to install all the dependencies
which are needed for this project as shown below
npm i express
npm i jsonwebtoken
npm i mongoose
npm i bcrypt
npm i cookie-parser
npm i cors
Express
: This will be the web server which we will be using for our application
mongoose
: It will be dependency which we will be using for interacting with the Mongodb
database
bcrypt
: This will be used for hashing
the passwords
jsonwebtoken
This library will generate and verify the json
web token and let users login into the protected routes.
cookie-parser
: This dependency will allow users to create and read cookies inside the express application
Directory Structure
At the end of this app this will be the directory
structure you will be having as shown below
As you can see in the above directory structure of the project we are following the MVC
approach which ensures all the code will be divided into their respective modules. Model will be the data
used inside the application and the Controller
will be the actual logic which will be used to make the Auth
system. Apart from that we have also the Routes
folder which will consist of all the routes
used inside the express app. And middlewares folder will contain all the middleware or other functions used to make the app.
Starting a Basic Express App
Now guys we need to create the index.js
file inside the root directory which will be the starting point of the project. And copy paste the below code to start a basic express app. And also we will be connecting to the Mongodb
database as shown below
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
const express = require("express"); const cors = require("cors"); const mongoose = require("mongoose"); const authRoutes = require("./routes/authRoutes"); const cookieParser = require("cookie-parser"); const app = express(); app.listen(4000, (err) => { if (err) { console.log(err); } else { console.log("Server Started Successfully."); } }); mongoose .connect("mongodb://localhost:27017/jwt", { useNewUrlParser: true, useUnifiedTopology: true, }) .then(() => { console.log("DB Connetion Successfull"); }) .catch((err) => { console.log(err.message); }); app.use( cors({ origin: ["http://localhost:3000"], methods: ["GET", "POST"], credentials: true, }) ); app.use(cookieParser()); app.use(express.json()); app.use("/", authRoutes); |
As you can see we are importing the mongoose
module and then using the connect()
method to connect to the database here you need to pass the database URL
and then inside the callback function we are starting the express app at port 3000. And also we are passing different middlewares such as cookie-parser which is used to create and read cookies.
Initializing Routes & Controllers
Now we will be initializing the different routes
and controllers
necessary for this auth system. For this you need to create the routes
folder and inside it you need to create authRoutes.js
file and also create a controllers
folder and inside it create a authController.js
file as shown below
routes/authRoutes.js
1 2 3 4 5 6 7 8 9 10 |
const { register, login } = require("../controllers/authControllers"); const { checkUser } = require("../middlewares/authMiddleware"); const router = require("express").Router(); router.post("/", checkUser); router.post("/register", register); router.post("/login", login); module.exports = router; |
As you can see we are having different routes
or endpoints for different operations such as get
request to login and signup pages using the express
router. And same for post method as well. And here we are importing the controller
file at the top and then using the methods defined in it in the routes. Now we need to define the controller
file as well. Lastly here we are exporting the router from this file.
controllers/authController.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
const User = require("../model/authModel"); const jwt = require("jsonwebtoken"); const maxAge = 3 * 24 * 60 * 60; const createToken = (id) => { return jwt.sign({ id }, "kishan sheth super secret key", { expiresIn: maxAge, }); }; const handleErrors = (err) => { let errors = { email: "", password: "" }; console.log(err); if (err.message === "incorrect email") { errors.email = "That email is not registered"; } if (err.message === "incorrect password") { errors.password = "That password is incorrect"; } if (err.code === 11000) { errors.email = "Email is already registered"; return errors; } if (err.message.includes("Users validation failed")) { Object.values(err.errors).forEach(({ properties }) => { errors[properties.path] = properties.message; }); } return errors; }; module.exports.register = async (req, res, next) => { try { const { email, password } = req.body; const user = await User.create({ email, password }); const token = createToken(user._id); res.cookie("jwt", token, { withCredentials: true, httpOnly: false, maxAge: maxAge * 1000, }); res.status(201).json({ user: user._id, created: true }); } catch (err) { console.log(err); const errors = handleErrors(err); res.json({ errors, created: false }); } }; module.exports.login = async (req, res) => { const { email, password } = req.body; try { const user = await User.login(email, password); const token = createToken(user._id); res.cookie("jwt", token, { httpOnly: false, maxAge: maxAge * 1000 }); res.status(200).json({ user: user._id, status: true }); } catch (err) { const errors = handleErrors(err); res.json({ errors, status: false }); } }; |
As you can see in the above code we have defined all the four methods
that we have used inside the routes
file for now we are just returning the simple
messages for the post request. But later on we will be registering and logging the users. For the get requests we are rendering the `
Defining the Model & Schema
Now guys we will be defining the models
and the schema
which will be used for this auth system. For this you need to make the models
folder and create a authModel.js
file as shown below
models/authModel.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
const mongoose = require("mongoose"); const bcrypt = require("bcrypt"); const userSchema = new mongoose.Schema({ email: { type: String, required: [true, "Email is Required"], unique: true, }, password: { type: String, required: [true, "Password is Required"], }, }); userSchema.pre("save", async function (next) { const salt = await bcrypt.genSalt(); this.password = await bcrypt.hash(this.password, salt); next(); }); userSchema.statics.login = async function (email, password) { const user = await this.findOne({ email }); if (user) { const auth = await bcrypt.compare(password, user.password); if (auth) { return user; } throw Error("incorrect password"); } throw Error("incorrect email"); }; module.exports = mongoose.model("Users", userSchema); |
Adding Auth Middlewares
Now we will be adding the auth
middlewares which will be necessary to show the user
details inside the dashboard page and also we are checking if the jwt token
is present inside the cookie or not. Now we need to create the middlewares
folder and inside it we need to make the authMiddleware.js
file
middlewares/authMiddleware.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
const User = require("../model/authModel"); const jwt = require("jsonwebtoken"); module.exports.checkUser = (req, res, next) => { const token = req.cookies.jwt; if (token) { jwt.verify( token,"secret key", async (err, decodedToken) => { if (err) { res.json({ status: false }); next(); } else { const user = await User.findById(decodedToken.id); if (user) res.json({ status: true, user: user.email }); else res.json({ status: false }); next(); } } ); } else { res.json({ status: false }); next(); } }; |
As you can see we are defining two methods in which we check the current
user if the jwt
token is present inside the cookie or not and then we are verifying the token using the verify()
method with the secret. And then we are setting the user to the res.locals
property so that we can show the details inside the dashboard
page.
Making the React.js Frontend
Now guys we will be making the frontend
for consuming this auth
backend api now first of all we need to create a new new react.js project using the below command
npx create-react-app authapp
cd authapp
Now we need to install the below packages
npm i react-toastify
npm i react-router-dom
npm i react-cookie
npm i axios
Now you will see the below directory
structure of the react.js app as shown below
Now first of all you need to edit the App.js
file of your react.js project and copy paste the following code
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React from "react"; import Register from "./pages/Register"; import { Routes, Route, BrowserRouter } from "react-router-dom"; import Login from "./pages/Login"; import Cards from "./pages/Cards"; import "react-toastify/dist/ReactToastify.css"; export default function App() { return ( <BrowserRouter> <Routes> <Route exact path="/register" element={<Register />} /> <Route exact path="/login" element={<Login />} /> <Route exact path="/" element={<Cards />} /> </Routes> </BrowserRouter> ); } |
As you can see we are initializing the react router
and we are declaring all the routes
that we will be having for the react.js aplication which will be register
login and the home
route. And each route is having the path
property and the element property which will render the actual component
.
Now we need to create the pages
directory where we will be declaring all the components
for the application
pages/Login.jsx
First of all we will having the login
form where we will be having the simple user html 5 form where we will allow the user to enter the email
and password
and then we will have the login button as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
import React, { useState, useEffect } from "react"; import axios from "axios"; import { Link, useNavigate } from "react-router-dom"; import { useCookies } from "react-cookie"; import { ToastContainer, toast } from "react-toastify"; function Login() { const [cookies] = useCookies([]); const navigate = useNavigate(); useEffect(() => { if (cookies.jwt) { navigate("/"); } }, [cookies, navigate]); const [values, setValues] = useState({ email: "", password: "" }); const generateError = (error) => toast.error(error, { position: "bottom-right", }); const handleSubmit = async (event) => { event.preventDefault(); try { const { data } = await axios.post( "http://localhost:4000/login", { ...values, }, { withCredentials: true } ); if (data) { if (data.errors) { const { email, password } = data.errors; if (email) generateError(email); else if (password) generateError(password); } else { navigate("/"); } } } catch (ex) { console.log(ex); } }; return ( <div className="container"> <h2>Login to your Account</h2> <form onSubmit={(e) => handleSubmit(e)}> <div> <label htmlFor="email">Email</label> <input type="email" name="email" placeholder="Email" onChange={(e) => setValues({ ...values, [e.target.name]: e.target.value }) } /> </div> <div> <label htmlFor="password">Password</label> <input type="password" placeholder="Password" name="password" onChange={(e) => setValues({ ...values, [e.target.name]: e.target.value }) } /> </div> <button type="submit">Submit</button> <span> Don't have an account ?<Link to="/register"> Register </Link> </span> </form> <ToastContainer /> </div> ); } export default Login; |
As you can see we are taking the user submitted
values and then we are making the fetch
request to the backend express server for loggin in and also we are showing the error
messages with the help of the react toastify
library and then if the login is successful we are redirecting the user to the secret
dashboard page.
Now we need to create another file
called Register.jsx
which will be simple register form where we allow the user to enter the same email and password and this time to register
into the database as shown below
pages/Register.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import React, { useState, useEffect } from "react"; import axios from "axios"; import { ToastContainer, toast } from "react-toastify"; import { useCookies } from "react-cookie"; import { Link, useNavigate } from "react-router-dom"; function Register() { const [cookies] = useCookies(["cookie-name"]); const navigate = useNavigate(); useEffect(() => { if (cookies.jwt) { navigate("/"); } }, [cookies, navigate]); const [values, setValues] = useState({ email: "", password: "" }); const generateError = (error) => toast.error(error, { position: "bottom-right", }); const handleSubmit = async (event) => { event.preventDefault(); try { const { data } = await axios.post( "http://localhost:4000/register", { ...values, }, { withCredentials: true } ); if (data) { if (data.errors) { const { email, password } = data.errors; if (email) generateError(email); else if (password) generateError(password); } else { navigate("/"); } } } catch (ex) { console.log(ex); } }; return ( <div className="container"> <h2>Register Account</h2> <form onSubmit={(e) => handleSubmit(e)}> <div> <label htmlFor="email">Email</label> <input type="email" name="email" placeholder="Email" onChange={(e) => setValues({ ...values, [e.target.name]: e.target.value }) } /> </div> <div> <label htmlFor="password">Password</label> <input type="password" placeholder="Password" name="password" onChange={(e) => setValues({ ...values, [e.target.name]: e.target.value }) } /> </div> <button type="submit">Submit</button> <span> Already have an account ?<Link to="/login"> Login</Link> </span> </form> <ToastContainer /> </div> ); } export default Register; |
As you can see we have the form and we are taking the user
submitted values and then we are making the fetch api request to the backend express server to register
the user and then we are showing the error messages with the help of toastify library. And redirecting to secret page once successfully registered.
Now we need to make the Cards.jsx
file where we will be showing the secret
page to the user once they are authenticated as shown below
pages/Cards.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import React, { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useCookies } from "react-cookie"; import axios from "axios"; import { toast, ToastContainer } from "react-toastify"; export default function Cards() { const navigate = useNavigate(); const [cookies, setCookie, removeCookie] = useCookies([]); useEffect(() => { const verifyUser = async () => { if (!cookies.jwt) { navigate("/login"); } else { const { data } = await axios.post( "http://localhost:4000", {}, { withCredentials: true, } ); if (!data.status) { removeCookie("jwt"); navigate("/login"); } else toast(`Hi ${data.user} 🦄`, { theme: "dark", }); } }; verifyUser(); }, [cookies, navigate, removeCookie]); const logOut = () => { removeCookie("jwt"); navigate("/login"); }; return ( <> <div className="private"> <h1>Super Secret Page</h1> <button onClick={logOut}>Log out</button> </div> <ToastContainer /> </> ); } |
As you can see we have the simple function
running inside the useEffect
hook to constantly check if the cookie is present inside the user browser they have the jwt
token which proves that the user is authenticated to access the page and then we show the page and a logout button also.