Unit-3 Back End (Autosaved)
Unit-3 Back End (Autosaved)
2.express-validator
It's a server-side data validation library. So, even if a malicious user bypasses the
client-side verification,
the server-side data validation will catch it and throw an error.
3.body-parser
It is nodejs middleware for parsing the body data.
4.bcryptjs
This library will be used to hash the password and then store it to database.
5.jsonwebtoken
jsonwebtoken will be used to encrypt our data payload on registration and return
a token.
6.mongoose
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous
environment.
Initiate Project
We will start by creating a node project. So, Create a new folder with the
name 'node-auth' and follow the steps below. All the project files should be
inside the 'node-auth' folder.
npm init
npm init will ask you some basic information about project. Now, you have
created the node project, it's time to install the required packages. So, go
ahead and install the packages by running the below command.
npm install express express-validator body-parser bcryptjs jsonwebtoken
mongoose --save
Now, create a file index.js and add this code.
// File : index.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
// PORT
const PORT = process.env.PORT || 4000;
app.get("/", (req, res) => {
res.json({ message: "API Working" });
});
app.listen(PORT, (req, res) => {
console.log(`Server Started at PORT ${PORT}`);
});
• If you type node index.js in the terminal, the server will start at PORT
4000.
• You have successfully set up your NodeJS app application. It's time to set
up the database to add more functionality.
Setup MongoDB Database db.js in config folder
const mongoose=require("mongoose");
constmongoDB_Url=process.env.MONGODB_URL;
mongoose.connect('mongodb://127.0.0.1:27017/authors');
mongoose.connection.on('error',err=>{
console.log(err);
});
mongoose.connection.on('connected',res=>{
console.log('connected');
});
• Now, we are done the database connection. Let's create
the User Model to save our registered users.
• Go ahead and create a new folder named model. Inside
the model folder, create a new file User.js.
• We will be using mongoose to create UserSchema.
User.js in model folder
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now()
}
});
// export model user with UserSchema
module.exports = mongoose.model("user", UserSchema);
Now, we are done with Database Connection, User Schema. So, let's go
ahead and update our index.js to connect our API to the database.
const express = require("express");
const bodyParser = require("body-parser");
const InitiateMongoServer = require("./config/db");
// Initiate Mongo Server
InitiateMongoServer();
const app = express();
const PORT = process.env.PORT || 4000;
// Middleware
app.use(bodyParser.json());
/**
* @method - POST
* @param - /signup
* @description - User SignUp
*/
• router.post(
• "/signup",
• [
• check("username", "Please Enter a Valid Username")
• .not()
• .isEmpty(),
• check("email", "Please enter a valid email").isEmail(),
• check("password", "Please enter a valid password").isLength({
• min: 6
• })
• ],
• async (req, res) => {
• const errors = validationResult(req);
• if (!errors.isEmpty()) {
• return res.status(400).json({
• errors: errors.array()
• });
• const {
• username,
• email,
• password
• } = req.body;
• try {
• let user = await User.findOne({
• email
• });
• if (user) {
• return res.status(400).json({
• msg: "User Already Exists"
• });
• }
• user = new User({
• username,
• email,
• password
• });
• await user.save();
• const payload = {
• user: {
• id: user.id
• }
• };
• jwt.sign(
• payload,
• "randomString", {
• expiresIn: 10000
• },
• (err, token) => {
• if (err) throw err;
• res.status(200).json({
• token
• });
• }
• );
• } catch (err) {
• console.log(err.message);
• res.status(500).send("Error in Saving");
• }
• }
• );
• Now, we have created the user registration in 'routes/user.js'.
So, we need to import this in index.js to make it work.
• So, the updated index file code should look like this.
• index.js
• const express = require("express");
• const bodyParser = require("body-parser");
• const user = require("./routes/user"); //new addition
• const InitiateMongoServer = require("./config/db");
• // Initiate Mongo Server
• InitiateMongoServer();
• const app = express();
• const PORT = process.env.PORT || 4000;
• app.use(bodyParser.json());
Now, it's time to implement the Login router which will be mounted on
'/user/login'.
Here is the code snippet for login functionality. Add the below code snippet in
user.js
• router.post(
• "/login",
• [
• check("email", "Please enter a valid email").isEmail(),
• check("password", "Please enter a valid password").isLength({
• min: 6
• })
• ],
• async (req, res) => {
• const errors = validationResult(req);
• if (!errors.isEmpty()) {
• return res.status(400).json({
• errors: errors.array()
• });
• }
• const { email, password } = req.body;
• try {
• let user = await User.findOne({
• email
• });
• if (!user)
• return res.status(400).json({
• message: "User Not Exist"
• const isMatch = await bcrypt.compare(password, user.password);
• if (!isMatch)
• return res.status(400).json({
• message: "Incorrect Password !"
• });
• const payload = {
• user: {
• id: user.id
• }
• };
• jwt.sign(
• payload,
• "randomString",
• {
• expiresIn: 3600
• },
• (err, token) => {
• if (err) throw err;
• res.status(200).json({
• token
• });
• }
• );
• } catch (e) {
• console.error(e);
• res.status(500).json({
• message: "Server Error"
• });
• }
Get LoggedIn User
Now, your User Signup and User Login is working, and you are getting a token in
return.
So, our next task will be to Retrieve the LoggedIn user using the token. Let's go and
add this functionality.
The /user/me route will return your user if you pass the token in the header. In the
file route.js, add the below code snippet.
• /**
• * @method - GET
• * @description - Get LoggedIn User
• * @param - /user/me
• */
• middleware/auth.js
middleware/auth.js
const jwt = require("jsonwebtoken");
module.exports = function(req, res, next) {
const token = req.header("token");
if (!token) return res.status(401).json({ message: "Auth Error" });
try {
const decoded = jwt.verify(token, "randomString");
req.user = decoded.user;
next();
} catch (e) {
console.error(e);
res.status(500).send({ message: "Invalid Token" });
}
How to Test the application?
PostMan is required for Testing the API. If you don't have PostMan
installed first, install it.
First, register the user or login if you are already registered.
From step 1, you will get a token. Copy that token and put in the header.
Hit Submit
Here is a preview of testing.
Some of Postman's top benefits include:
• Easy to create, share, test, and document APIs.
• Store information for running tests in different environments.
• Store data for use in other tests.
• Integrates with build systems.
• Easy to move tests and environments to code repositories.
• Good UI.
JSON web token | JWT
A JSON web token(JWT) is JSON Object which is used to securely transfer information
over the web(between two parties). It can be used for an authentication system and can also
be used for information exchange. The token is mainly composed of header, payload,
signature. These three parts are separated by dots(.). JWT defines the structure of
information we are sending from one party to the another, and it comes in two forms –
Serialized, Deserialized. The Serialized approach is mainly used to transfer the data through
the network with each request and response. While the deserialized approach is used to read
and write data to the web token.
Deserialized Header
A header in a JWT is mostly used to describe the cryptographic
operations applied to the JWT like signing/decryption technique used
on it. It can also contain the data about the media/content type of the
information we are sending. This information is present as a JSON
object then this JSON object is encoded to BASE64URL. The
cryptographic operations in the header define whether the JWT is
signed/unsigned or encrypted and are so then what algorithm
techniques to use. A simple header of a JWT looks like the code
below:
{
"typ":"JWT",
"alg":"HS256"
Payload
The payload is the part of the JWT where all the user data is actually added. This data is also
referred to as the ‘claims’ of the JWT. This information is readable by anyone so it is always
advised to not put any confidential information in here. This part generally contains user
information. This information is present as a JSON object then this JSON object is encoded
to BASE64URL. We can put as many claims as we want inside a payload, though unlike
header, no claims are mandatory in a payload. The JWT with the payload will look
something like this:
{
"userId":"b07f85be-45da",
"iss": "https://provider.domain.com/",iss (issuer), exp (expiration time), sub
(subject), and aud (audience).
"sub": "auth/some-hash-here",
"exp": 153452683
}
Serialized Signature
[header].[payload].[signature]
allthese three components make up the serialized JWT. We already
know what header and payload are and what they are used for.Let’s
talk about signature.
Signature
This is the third part of JWT and used to verify the authenticity of token. BASE64URL encoded header and
payload are joined together with dot(.) and it is then hashed using the hashing algorithm defined in a header
with a secret key. This signature is then appended to header and payload using dot(.) which forms our actual
token header.payload.signature
Syntax :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2Nzg5LCJuYW1lIjoiSm9zZXBoIn0.OpOSSw
7e485LOP5PrzScxHb7SR6sAOMRckfFwi4rp7o
JWT Tokens
Use Cases:
1. Authentication: JWTs are often used for user authentication. When a user logs in, a JWT is issued,
which is then used to identify the user in subsequent requests.
2. Information Exchange: JWTs can be used to securely exchange information between parties, such as
in single sign-on (SSO) systems, where user identity is passed between different services or domains.
Benefits:
3. Stateless: JWTs are stateless, meaning the server doesn't need to keep track of token states. This
makes them scalable and suitable for microservices architectures.
4. Compact: JWTs are compact and can be easily transmitted as URL parameters or in the headers of
HTTP requests.
5. Self-Contained: JWTs carry information (claims) within the token itself, eliminating the need to query a
database or validate the token against the issuer.
JWT Tokens
Security Considerations:
• Data Integrity: JWTs are signed, ensuring the data's integrity. However,
they do not provide confidentiality, so sensitive information should not
be stored in them without proper encryption.
• Expiration: JWTs should have short lifetimes, and proper refresh
mechanisms should be in place to minimize the risk of token theft or
unauthorized access.
• Signature Key Management: The security of JWTs relies on the
management of the signing key. If the key is compromised, the entire
system is at risk.
OAuth Tokens
Use Cases:
1. Authorization: OAuth tokens are primarily used for authorization. They grant access to resources or
actions on behalf of a user, without sharing the user's credentials.
2. Third-Party Access: OAuth is designed for scenarios where third-party applications or services need
controlled access to a user's resources (e.g., social media logins or API access).
Benefits:
3. Granular Access Control: OAuth allows fine-grained control over what resources a client (third-party
app) can access, based on scopes and permissions.
4. Revocable Access: OAuth tokens can be revoked, which is important for managing access to user data
when a user decides to de-authorize an application.
5. User Consent: OAuth includes a user consent step, where the user explicitly grants or denies access to
their data.
• Security Considerations:
• Token Leakage: OAuth tokens are sensitive and should be
protected from theft or leakage. This can be done through secure
storage and transmission.
• Token Expiration: Proper token expiration and refresh
mechanisms are essential to mitigate the risk of long-lived tokens
being compromised.
• OAuth Version and Implementation: The security of OAuth
largely depends on the specific version and the implementation.
Careful consideration and best practices are needed.
Difference between JWT and Oauth
• Use Case: JWT is more for authentication and information exchange, while OAuth is
specifically designed for authorization and access delegation.
• Data Storage: JWTs store claims within the token, while OAuth tokens often reference data
stored on the authorization server.
• Authorization vs. Authentication: OAuth is primarily about authorization, while JWT can be
used for user authentication.
• Token Lifetime: OAuth tokens typically have shorter lifetimes and can be refreshed, while
JWTs can have longer lifetimes but should be used with refresh tokens for security.
• Revocation: OAuth tokens can be revoked, whereas JWTs are typically valid until they expire.
• User Consent: OAuth includes a user consent step, which is not a standard feature of JWT-
based authentication.
• Security: Both JWT and OAuth tokens require proper security measures, but the specific risks
and considerations differ.
Basic Authentication in Node.js using HTTP Header
Authentication of the client is the first step before starting any Application. The basic
authentication in the Node.js application can be done with the help express.js framework.
Express.js framework is mainly used in Node.js application because of its help in handling
and routing different types of requests and responses made by the client using different
Middleware.
In the example, we have created the basic server using express. Also,
we have used the ‘cookieParser’ in the express app.
Whenever the user goes to the ‘setcookies’ route, it sets the car data
in the cookies. Users can go to the ‘getcookies’ route to access all
cookies. Users can go on the ‘clear’ route to clear the cookies.
Example 1
let express = require("express");
let cookieParser = require("cookie-parser");
//setup express app
let app = express();
app.use(cookieParser());
// set cookies for table and homeWindow with different expiry time
app.get("/setCookies", (req, res) => {
res.cookie("table", table, { maxAge: 900000, httpOnly: true });
res.cookie("homeWindow", homeWindow, { maxAge: 600000, httpOnly: true });
res.send("Cookies are set with different expiry time");
});
// get cookies
app.get("/getCookies", (req, res) => {
res.send(req.cookies);
});
// get cookies with specific name
app.get("/getCookies/:name", (req, res) => {
res.send(req.cookies[req.params.name]);
});
// delete cookies with specific name
app.get("/deleteCookies/:name", (req, res) => {
res.clearCookie(req.params.name);
res.send("Cookies with name " + req.params.name + " is deleted");
});
//server listens to port 3000
app.listen(8000, (err) => {
if (err) throw err;
console.log("listening on port 8000");
Advantages of Cookies in web applications
• Session Management: Cookies are commonly used to manage user sessions.
When a user logs in to a website, a session cookie is often created. This
cookie contains a unique identifier that allows the server to associate
subsequent requests with that user's session. This is crucial for maintaining
user authentication and enabling personalized experiences during a single
visit to the site.
• User Authentication: Cookies are essential for user authentication. They
store information that verifies a user's identity, such as a user ID or a token.
When a user logs in, a cookie is set to maintain their authenticated state, and
subsequent requests are checked against this cookie to ensure the user is still
logged in.
• Personalization: Cookies are frequently used to personalize the user
experience. They can store user preferences, such as language settings, theme
choices, or product recommendations. When users return to the website, the
server can read these cookies and present a customized experience.
Advantages of Cookies in web applications
• Shopping Carts: In e-commerce applications, cookies are used to
maintain shopping cart contents. When users add items to their cart,
this information is stored in a cookie. This allows users to browse the
site, return later, and still see their selected items in the cart.
• Tracking and Analytics: Cookies are employed for tracking user
behavior and collecting analytics data. Tools like Google Analytics
use cookies to track user sessions, page views, and other interactions,
providing website owners with valuable insights for optimizing their
web applications.
• Remember Me Functionality: Many websites offer a "Remember
Me" option during login. When a user selects this option, a persistent
cookie is created that keeps them logged in across sessions. This is
convenient for users who want to avoid repeated logins.
Advantages of Cookies in web applications
• Security: Cookies can enhance security by preventing cross-
site request forgery (CSRF) attacks. Anti-CSRF tokens are
often stored in cookies to validate the authenticity of requests.
• Load Balancing: In cases of distributed web applications,
cookies can store information about the user's preferred server,
helping to evenly distribute the load among multiple servers.
• Tracking User Progress: In multi-step processes, such as
form submissions or online surveys, cookies can be used to
track the user's progress and allow them to resume from where
they left off.
• Ad Targeting: Cookies are also utilized by advertisers to track
user behavior and provide targeted advertisements. Ad
networks use cookies to identify users who have visited
specific sites and display ads that match their interests.
Disadvantages of Cookies
Using cookies in web applications can introduce several security concerns. To
ensure the safety and privacy of users, it's essential to be aware of these issues and
implement mitigation strategies:
Cookie Theft through Insecure Wi-Fi: When using public Wi-Fi, data in
cookies can be intercepted and stolen, compromising user accounts.
Solution: Encourage users to connect via secure networks whenever
possible. Use HTTPS to encrypt data in transit, and consider implementing
HTTP Strict Transport Security (HSTS) to ensure secure connections.
Persistent Cookies: Long-lived or persistent cookies can be a security risk if
they are stolen, as they remain valid for extended periods.
Solution: Limit the use of persistent cookies, especially for sensitive data.
Implement strict expiration policies for cookies and encourage users to log
out of their accounts when they're done to invalidate session cookies.
Disadvantages of Cookies
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Express session middleware within a
Node.js application.
Express session middleware is an essential component in a Node.js
application, particularly when using the Express.js framework. It plays a
crucial role in handling and managing user sessions, which are a
fundamental aspect of web applications.
Session Management: The primary role of Express session middleware is
to manage user sessions. A session is a temporary and unique interaction
between a user and a web application. It allows the application to
recognize and remember a user as they navigate through the site without
requiring them to authenticate with each request. This is essential for
maintaining user state and providing a personalized experience.
Data Storage: Express session middleware stores session data, such as
user authentication information or user-specific settings, on the server.
This ensures that sensitive user data isn't exposed on the client-side and
is protected from tampering or unauthorized access.
Express session middleware within a
Node.js application.
• Session ID: It generates and assigns a unique session
identifier, usually in the form of a session cookie, to each
client's browser. This session ID is used to link the client
to their session data stored on the server.
• Authentication: It is often used in conjunction with user
authentication mechanisms. For example, after a user logs
in, the session middleware can store a user's credentials or
user ID in the session data, making it easy to check if a
user is authenticated on subsequent requests.
• State Management: It helps in maintaining the state of a
user's interactions with the application. For instance, it
can store a user's shopping cart items, language
preferences, or any other relevant data that needs to
persist during their visit.
Express session middleware within a
Node.js application.
• Security: Express session middleware can be configured to
enhance the security of user sessions. It can prevent
common security issues like session fixation, session
hijacking, and session timeout management.
• Middleware Integration: It integrates seamlessly into the
Express.js middleware stack. This means it can be easily
added to your application, and you can configure it to work
with other middlewares, such as authentication middleware
or logging middleware.
• Customization: Express session middleware allows you to
configure session storage options, session expiration, and
other settings to tailor it to your specific application
Express.js route handler for creating user session
when a client connect
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'your-secret-key', // Change this to a strong, random value in production
resave: false,
saveUninitialized: true,
}));
// Simulated user data (you would typically retrieve this from a database)
const users = [
{ id: 1, username: 'user1', password: 'password1' },
{ id: 2, username: 'user2', password: 'password2' },
// Middleware to parse JSON request bodies
app.use(express.json());
// Login route
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Simulated authentication logic (replace with your authentication mechanism)
const user = users.find(u => u.username === username && u.password === password);
if (user) {
// Set session data when the user is authenticated
req.session.user = user;
res.status(200).send('Login successful');
} else {
res.status(401).send('Login failed');
}
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
Difference between Cookies and Session
Cookies Session
Cookies are small text files
Sessions are used to store
used to store user
user information on the
information on the user’s
user’s server side.
computer.
A session ends when the user
Cookies expire after a
closes the browser or logs
specified lifetime or duration.
out.
Cookies can only store a
Sessions have a 128 MB size
limited amount of data of
to store data for one time.
4KB in a browser.
Cookies store information in a Session stores data in an
text file. encrypted format.
Testing
When testing with Node.js, you’ll typically use either Mocha, Chai, Jest, Chai HTTP, or Sinon.
Unit testing
Unit testing involves testing your application’s code and logic, which includes anything that your
application can do on its own without having to rely on external services or data.
Integration testing
Integration testing involves testing your application as it connects with services inside or outside of the
application.
Regression testing
Regression testing involves testing your application’s behavior after a set of changes have been made.
End-to-end testing
End-to-end testing involves testing the full end-to-end flow of your project, including external HTTP
calls and complete flows within your project.
Component testing
Component testing involves testing the totality of a service, including the modules and external
services connected to it. However, it’s expensive to perform because it takes a lot of time.
How to Test Node.js REST APIs Using Mocha, Chai, and
Chai-HTTP
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>LOGIN</h1>
<form class="" action="/login" method="post">
<input type="email" name="username"
placeholder="Name" value="">
<input type="password" name="password"
placeholder="Password" value="">
<button type="submit" name="button">
Submit
</button>
</form>
</body>
How to handle errors in node.js
Error handling is a mandatory step in application development. A
Node.js developer may work with both synchronous and asynchronous
functions simultaneously. Handling errors in asynchronous functions is
important because their behavior may vary, unlike synchronous
functions. While try-catch blocks are effective for synchronous
functions, asynchronous functions can be dealt with callbacks,
promises, and async-await. Try-catch is synchronous means that if an
asynchronous function throws an error in a synchronous try/catch
block, no error throws. Errors thrown in Node.js applications can be
handled in the following ways:
console.log("Program Ended");
Output:
Program Ended
[Error: ENOENT: no such file or directory,
open 'C:\Users\User\Desktop\foo.txt'] {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'C:\\Users\\User\\Desktop\\foo.txt'
}
Explanation: In this case, the file does not exist in the system hence the error is thrown. Using promises and promise
callbacks: Promises are an enhancement to Node.js callbacks. When defining the callback, the value which is returned is
called a “promise”. The key difference between a promise and a callback is the return value. There is no concept of a
return value in callbacks. The return value provides more control for defining the callback function. In order to use
promises, the promise module must be installed and imported into the application. The .then clause handles the output of
the promise. If an error occurs in any .then clause or if any of the promises above are rejects, it is passed to the immediate
.catch clause. In case of a promise is rejected, and there is no error handler then the program terminates.
const Promise = require('promise');
const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost/TestDB';
MongoClient.connect(url)
.then(function (err, db) {
db.collection('Test').updateOne({
Output:
"Name": "Joe"
}, // In this case we assume the url is wrong
MongoError: failed to connect to server
{ [localhost:27017]
$set: {// error message may vary
"Name": "Beck"
}
});
})
Using async-await:
Output: