User Authentication and Authorization - Backend (Node.
js,
Express.js and MongoDB)
Task 1: Node.js and Express.js Installation and Configuration
1. In the new project directory. For example, name it as “USER-AUTHENTICATION”
NOTE: Follow the frontend instructions to create a new react app and implement
frontend. You may complete the implementation for backend first before implementing
frontend.
2. Create a new subdirectory, you can name it as ‘backend’ or ‘server’
mkdir backend
3. Your application folder should look like this:
4. In your terminal, get into the backend directory:
cd backend
5. Run the following command to initialize the backend application, Node.js to create a new
package.json file for a project with default settings, without asking the user any
questions.
npm init -y
The backend folder should contain a package.json file like this:
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
6. To install Express.js and other dependencies, run the following commands in your
terminal:
npm install express mongoose nodemon cors jsonwebtoken
bcryptjs cookie-parser dotenv
Dependencies Description
Express.js A fast and mimimalist Node.js web application framework.
mongoose A JS object-oriented programming library that creates a
connection between MongoDB and Node.js
nodemon A command-line tool to automatically restarts the node
application when file changes in the directory are detected
jsonwebtoken An authentication way for creating data with optional signature
and/or optional encryption.
A library for creating and verifying JSON Web Tokens (JWTs)
used for authentication.
bcryptjs A library for hashing passwords securely.
cors A middleware used to enable Cross-Origin Resource Sharing
(CORS) for an Express.js application.
Allowing or blocking web page access to resources on a
different domain.
dotenv Allow you to store configuration data in a .env file, which is
typically not committed to version control, to separate sensitive
information from your codebase
This file contains key-value pairs that represent the environment
variables.
cookie-parser Middleware that handles cookie-based sessions in incoming
HTTP requests
Extract information from cookies that may be required for
authentication or other purposes.
7. After installing the required dependencies, create a new file called index.js in the root
directory of your server sub folder of your application. The index.js file will contain our
Node.js server.
fsutil file createnew index.js 0
8. In the index.js file of your server, add the following code:
const express = require("express");
const app = express();
const PORT = 4000;
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
9. Before you start the server, update your package.json file in the backend by adding the
code below to make sure your application restarts on any update:
"scripts": {
"start": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
10. Now, you can start your server by running npm start in your terminal.
npm start
If all these are successfully executed, your terminal should look like this:
Task 2: Set Up MongoDB
The database MongoDB should be installed before following these steps to link your
database to your backend. Refer to installation guidelines to either:
a) Install MongoDB on your machine locally and access to your database using
MongoDB Compass or MongoDB Shell.
b) Setup your account using MongoDB Atlas (cloud-based database-as-a-service)
Steps:
1. Using the MongoDB Atlas, MongoDB Compass or MongoDB Shell to create a new
database named ‘jwt’ and a collection named ‘users’.
2. Copy connection string from your database:
Example of connection strings:
• Local machine:
mongodb://localhost:27017/jwt
• Cloud Machine:
mongodb+srv://<username>:<password>@cluster0.bm7jbbf.mongodb.n
et/?retryWrites=true&w=majority&appName=Cluster0
3. Create a .env file in your backend directory to store environment variables. Open
your .env file and create 3 environment variables that we need to pass to our
application’s environment like the code below:
a. MONGODB_URL: database connection link
b. PORT: specify port number on which we want our app to listen for connections.
c. TOKEN_KEY : a secret token key for jsonwebtoken (jwt)
MONGO_URL=mongodb://localhost:27017/jwt
PORT=4000
TOKEN_KEY=gfg_jwt_secret_key
4. Update the index.js file in the backtend directory with the code snippet below to:
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const app = express();
require("dotenv").config();
const { MONGO_URL, PORT } = process.env;
mongoose
.connect(MONGO_URL)
.then(() => console.log("MongoDB is connected successfully"))
.catch((err) => console.error(err));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
app.use(
cors({
origin: ["http://localhost:4000"],
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true,
})
);
app.use(express.json());
In the code above:
• We are configuring our application to be able to have access to the .env file. You
can get the information in your .env file by doing process.env.
• CORS: You can allow requests from other domains to access the resources on
your server by using the cors() express middleware function. The CORS headers
that your server should include in the response can be specified using the
function’s optional configuration object parameter, which is taken as a parameter
by the function which is the origin, methods and credentials.
• express.json(): The express.json() will add a body property to the request or req
object. This includes the request body's parsed JSON data. req.body in your
route handler function will allow you to access this data.
5. Now you can restart your application by doing npm start in your server directory.
npm start
Your terminal should look like the image below.
6. Create the following folders in the backend directory of your application:
• Controllers
• Middlewares
• Routes
• Models
• Util
Task 3: Handle the SIGNUP Route
1. Create a file called UserModel.js in the Models directory and put the following code into
it to get started:
a. The user schema and user password will be created in the code using
mongoose and bcryptjs, respectively, for security purposes.
b. The password is hashed using bcryptjs for security reasons prior to saving the
user.
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = new mongoose.Schema({
email: {
type: String,
required: [true, "Your email address is required"],
unique: true,
},
username: {
type: String,
required: [true, "Your username is required"],
},
password: {
type: String,
required: [true, "Your password is required"],
},
createdAt: {
type: Date,
default: new Date(),
},
});
userSchema.pre("save", async function () {
this.password = await bcrypt.hash(this.password, 12);
});
module.exports = mongoose.model("User", userSchema);
2. Next, setup a function to handle the generation of a token, which will be called
SecretToken.js in the util folder. Copy and paste the code below into the newly created
file (SecretToken.js):
require("dotenv").config();
const jwt = require("jsonwebtoken");
module.exports.createSecretToken = (id) => {
return jwt.sign({ id }, process.env.TOKEN_KEY, {
expiresIn: 3 * 24 * 60 * 60,
});
};
3. Create a file called AuthController.js in the Controllers directory and paste in the
following code:
a. The user's inputs are obtained from the req.body in the code, and you then
check the email to make sure no past registrations have been made.
b. We will use the values obtained from req.body to create the new user after that
has occurred. MongoDB always assigns a new user with a unique _id.
c. The newly formed user’s _id is then supplied as an parameter to the
createSecretToken() function, which handles token generation.
d. The cookie will be sent to the client side with key of "token", and value of
token.
const User = require("../Models/UserModel");
const { createSecretToken } = require("../util/SecretToken");
const bcrypt = require("bcryptjs");
module.exports.Signup = async (req, res, next) => {
try {
const { email, password, username, createdAt } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.json({ message: "User already exists" });
}
const user = await User.create({ email, password, username,
createdAt });
const token = createSecretToken(user._id);
res.cookie("token", token, {
withCredentials: true,
httpOnly: false,
});
res
.status(201)
.json({ message: "User signed in successfully", success: true,
user });
next();
} catch (error) {
console.error(error);
}
};
4. Next, create a file called AuthRoute.js in the Routes directory. Paste the code below into
the newly created file:
a. The /signup route has a post method attached to it
b. When it’s been called, the Signup controller (AuthController) will be executed.
const { Signup } = require("../Controllers/AuthController");
const router = require("express").Router();
router.post("/signup", Signup);
module.exports = router;
5. Next, update your index.js file so it can be aware of the routes. Your index.js file should
look like the code below:
a. The cookie-parser manages cookie-based sessions or extracts data from
cookies.
b. It’s added to the code above along with the authRoute that the application will
utilize.
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const app = express();
require("dotenv").config();
const cookieParser = require("cookie-parser");
const authRoute = require("./Routes/AuthRoute");
const { MONGO_URL, PORT } = process.env;
mongoose
.connect(MONGO_URL)
.then(() => console.log("MongoDB is connected successfully"))
.catch((err) => console.error(err));
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
app.use(
cors({
origin: ["http://localhost:3000"],
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true,
})
);
app.use(cookieParser());
app.use(express.json());
app.use("/", authRoute);
6. Now, test the /signup route (http://localhost:4000/signup) using POST method
with a tool called Postman. Make sure you’re in the backend directory in the terminal,
then run npm start to start your application.
Screenshot 1: MongoDB Compass (Create a new user successfully)
Screenshot 2: Postman (Create a new user successfully and the generated cookie
from the response.
Screenshot – Postman (Use a registered email to check the user already exists)
Task 4: Handle the LOGIN Route
1. Open the AuthController.js file in the Controllers directory, and update it with the code
below:
a. You are determining in the code above whether the email and password match
any previously stored user in the database.
module.exports.Login = async (req, res, next) => {
try {
const { email, password } = req.body;
if(!email || !password ){
return res.json({message:'All fields are required'})
}
const user = await User.findOne({ email });
if(!user){
return res.json({message:'Incorrect password or email' })
}
const auth = await bcrypt.compare(password,user.password)
if (!auth) {
return res.json({message:'Incorrect password or email' })
}
const token = createSecretToken(user._id);
res.cookie("token", token, {
withCredentials: true,
httpOnly: false,
});
res.status(201).json({ message: "User logged in successfully",
success: true });
next()
} catch (error) {
console.error(error);
}
}
2. Next, add the following code to the file AuthRoute.js in the Routes directory:
a. The /login route has a post method attached to it
b. When it’s been called, the Login controller (AuthController) will be executed.
const { Signup, Login } = require('../Controllers/AuthController')
const router = require('express').Router()
router.post('/signup', Signup)
router.post('/login', Login)
module.exports = router
3. Now, test the /login route (http://localhost:4000/login) using POST method
with a tool called Postman. Make sure you’re in the backend directory in the terminal,
then run npm start to start your application.
Screenshot 1 – Postman: If you try to use a registered email or password, you’ll
get the message below
Screenshot – Postman: If you try to use an unregistered email or password, you’ll
get the message below
Task 5: Handle the HOME Route
1. Create a AuthMiddleware.js file, in the Middlewares directory, and paste in the code
below to check whether the user has access to the route by checking if the tokens
match.
const User = require("../Models/UserModel");
require("dotenv").config();
const jwt = require("jsonwebtoken");
module.exports.userVerification = (req, res) => {
const token = req.cookies.jwt
if (!token) {
return res.json({ status: false })
}
jwt.verify(token, process.env.TOKEN_KEY, async (err, data) => {
if (err) {
return res.json({ status: false })
} else {
const user = await User.findById(data.id)
if (user) return res.json({ status: true, user: user.username })
else return res.json({ status: false })
}
})
}
2. Next, update the AuthRoute.js file in the Routes directory with the code below:
const { Signup, Login } = require('../Controllers/AuthController')
const { userVerification } = require('../Middlewares/AuthMiddleware')
const router = require('express').Router()
router.post('/signup', Signup)
router.post('/login', Login)
router.post('/',userVerification)
module.exports = router
3. Now, test the / route (http://localhost:4000/) using POST method with a tool called
Postman. Run npm start to start your backend application.
Screenshot – Postman: It should look like the image below
Task 6: Test both Backend and Frontend
1. After implementing the Frontend, you may test the user authentication and
verification by starting your backend ExpressJS and frontend React applications.
a. Run npm start in your terminal.
b. NOTE: To run the Frontend and Backend in one command only, you may
refer to the following reference (Use either “concurrently” library or “npm-run-
all”)
Reference: https://medium.com/@rwijayabandu/how-to-run-frontend-and-
backend-with-one-command-55d5f2ce952c
2. Test the Signup, Login and Home routes in your application to view the login, signup
and home pages
• http://localhost:3000/signup
• http://localhost:3000/login
• http://localhost:3000
Figure 1: Signup page
Figure 2: Home page
Figure 3: Login page