Mar 1, 2024 · 9 min read

Authentication is a crucial aspect of modern web applications. JSON Web Tokens (JWT) have become a popular method for implementing authentication in web applications because of it's simplicity, security, and scalability.
In this Article We'll discuss about What is JWT, How it works, and How to use it using a sample project!
So without delaying further, Let's Get Started!
Before we begin let's understand what is a JWT.
So JWT, which stands for JSON Web Token, is an open standard for securely sharing JSON data between parties. The data is encoded and digitally signed, which ensures its authenticity.
JWT is widely used in API authentication and authorization workflows, as well as for data transfer between clients and servers.
A JWT has three sections: a header, a payload, and a signature. Each sections are separated by periods. A typical JWT looks like this:
xxxxxx.yyyyyy.zzzzzz
Here the X’s represent the header, the Y’s represents the payload, and the Z’s represents the signature
An example token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQXJpbmRhbSIsImlhdCI6MTUxNjIzOTAyMn0._RETfYt1NardpwbQDm7z-t3Ri3ltOib8Vd6tCfyG5Nk
Header: It's a JSON object that contains information about the token, such as the algorithm used for the signature. The header is Base64Url encoded. Here’s an example:
{
"alg": "HS256",
"typ": "JWT"
}
Paylod: It's a JSON object containing the main data that we are trying to send, such as the user ID, permissions, and expiration time. The payload is also Base64Url encoded. Here’s an example
{
"name": "Arindam",
"iat": 1516239022
}
Signature: It is used to verify the token’s authenticity. The signature is calculated using a secret key known only to the server, and it is typically a combination of the header and the payload, encoded with the algorithm specified in the header. Here’s an example of the signature:
HMAC256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key);
If you want to play with JWT and put these concepts into practice, you can use jwt.ioDebugger to decode, verify, and generate JWTs.
In authentication, when the user successfully logs in using their credentials, it returns a JSON Web Token.
💡
Note: Don't store sensitive session data in browser storage due to lack of security.
And When the users want to access the protected route/private route, they have to send the JWT token typically in the Authorization header.
This works as a stateless authorization mechanism, where the server's protected routes will validate the JWT. If the JWT token is valid it sends the response to the client/user.
We'll understand it more in the next section by implementing this in the our Demo Project!
To start our project, we need to set up a Node.js environment. So, let's create a node project. Run the Following command in the Terminal.
npm init
This will initialize a new Node.js project.
Now, We'll install the required dependencies of our project.
npm install express mongoose jsonwebtoken dotenv
This will install the following packages:
express: a popular web framework for Node.js
mongoose: An ODM (Object Data Modeling) library for MongoDB.
jsonwebtoken: For generating and verifying JSON Web Tokens (JWT) for authentication.
dotenv: loads environment variables from a .env file
Next, we'll create a .env folder to securely store our sensitive information such as API credentials.
//.env
MONGODB_URL=<Your MongoDB Connection String>
JWT_SECRET= "your_secret_key_here"
PORT=3000
First to MongoDB Atlas and create an account or sign-in if you already have one.
Then we'll Get this Interface:
Then we'll Click on Build a Database, Here we'll get these options.
For this project we'll be using M0 that's the free tier.
After selecting the Tier and the Provider we'll click the button. Then we'll get something like this:
Here we'll set the username and Password. (Make sure to remember the Password)
Then Scroll a bit and set the IP to 0.0.0.0/0 and Everywhere. This means Our database can be acessible from everywhere.
Now we'll click the Finish button. It will initiate the deployment of our cluster.
And,We'll get something like this:
After that We'll get a dashboard like this:
On this Home page of the cluster, we'll click on the connect button.
Then, the following window will appear and then we'll click on the Compass link.
After that, we'll get something like this:
If you haven't installed MongoDB Compass you have to install it first. I've installed it on my machine so I have selected that.
Now We'll copy the connection string.
In my case it's mongodb+srv://<username>:<password>@cluster0.io7vzwv.mongodb.net/ . Replace the username & password with your own username & password.
Now we'll Open MogoDb Compass and paste the connection string to connect with our cluster.
After Connecting we'll get this interface!
Awesome! we have successfully created a MongoDb database.
Now, we'll create a index.js file in the root directory and set up a basic express server. See the following code:
const express = require("express");
const dotenv = require("dotenv");
const mongoose = require("mongoose");
dotenv.config();
const app = express();
const port = process.env.PORT;
//middleware provided by Express to parse incoming JSON requests.
app.use(express.json());
mongoose.connect(process.env.MONGODB_URL).then(() => {
console.log("MongoDB is connected!");
});
app.get("/", (req, res) => {
res.send("Hello World");
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Here, We're using the "dotenv" package to access the PORT number from the .env file.
At the top of the project, we're loading environment variables using dotenv.config() to make it accessible throughout the file.
In this step, we'll add a start script to the package.json file to easily run our project.
By using the command node index.js, we have to restart your server each time when you make changes to your file. To avoid this we can install nodemon using the following command.
npm install nodemon
So, Add the following script to the package.json file.
"scripts": {
"start": "nodemon index.js"
}
The package.json file should look like this:
To check whether everything is working or not, let's run the project using the following command:
npm run start
This will start the Express server. Now if we go to this URL http://localhost:3000/ we'll get this:
Awesome! The Project setup is done and it's working perfectly. Next up, we'll add Gemini to our project in the next section
Till now we have done the basic project setup now we'll Create a new directory named “models” in our root directory and inside it, create a new file named User.js.
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
});
module.exports = mongoose.model("User", userSchema);
Here we have created a simple schema for our project.
Next up, we'll create authentication Route for users to sign up and login.
const express = require("express");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const router = express.Router();
// Signup route
router.post("/signup", async (req, res) => {
try {
const { username, password } = req.body;
const user = new User({ username, password });
await user.save();
res.status(201).json({ message: "New user registered successfully" });
} catch (error) {
res.status(500).json({ message: "Internal server error" });
}
});
// Login route
router.post("/login", async (req, res) => {
const { username, password } = req.body;
try {
const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ message: "Invalid username or password" });
}
if (user.password !== password) {
return res.status(401).json({ message: 'Invalid username or password' });
}
// Generate JWT token
const token = jwt.sign(
{ id: user._id, username: user.username },
process.env.JWT_SECRET
);
res.json({ token });
} catch (error) {
res.status(500).json({ message: "Internal server error" });
}
});
module.exports = router;
Here, the signup route allows users to register by providing a username and password. Upon receiving the data, it creates a new user instance and saves it to the database.
And, the login route verifies the user's credentials against the database. If the username and password match, it generates a JWT token and returns it to the client.
Here we have added one middleware function verifyJWT that verifies the JWT token sent by the client and authenticate the user.
const jwt = require("jsonwebtoken");
function verifyJWT(req, res, next) {
const token = req.headers["authorization"];
if (!token) {
return res.status(401).json({ message: "Access denied" });
}
jwt.verify(token, process.env.JWT_SECRET, (err, data) => {
if (err) {
return res.status(401).json({ message: "Failed to authenticate token" });
}
req.user = data;
next();
});
}
module.exports = verifyJWT;
Finally, We'll integrate these authentication routes with our Express application in the index.js file.
const express = require("express");
const dotenv = require("dotenv");
const mongoose = require("mongoose");
const userMiddleware = require("./middleware/user");
const authRouter = require("./routes/auth");
dotenv.config();
const app = express();
const port = process.env.PORT;
//middleware provided by Express to parse incoming JSON requests.
app.use(express.json());
mongoose.connect(process.env.MONGODB_URL).then(() => {
console.log("MongoDB is connected!");
});
app.use('/auth', authRouter);
app.get("/protected", userMiddleware, (req, res) => {
const { username } = req.user;
res.send(`This is a Protected Route. Welcome ${username}`);
});
app.get("/", (req, res) => {
res.send("Hello World");
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Here We have created a /protected route which uses userMiddleware to checks if the user is authenticated.
If the user is authenticated , the server sends back a response saying "This is a Protected Route. Welcome [username]".
Here, [username] is replaced with the username of the authenticated user.
To validate the SignUp Endpoint, we'll make a POST Request to this route http://localhost:3000/auth/signup with Headers Content-Type : application/json and the following JSON body:
{
"username": "Arindam",
"password": "071227"
}
Awesome Our Signup Endpoint is working perfectly.
To validate the LogIn Endpoint, we'll make a POST Request to this route http://localhost:3000/auth/login with Headers Content-Type : application/json and the following JSON body:
{
"username": "Arindam",
"password": "071227"
}
And It's working perfectly! We got the token back as a response. We will copy the token as we'll need this token in the next section.
Now that we have obtained a JWT token from the login endpoint, we will test the protected route by including this token in the request headers.
For that, We'll Make a GET request to the protected route URL http://localhost:3000/protected . And In the request headers,we'll include the JWT token obtained from the login endpoint:
Yay! It's Working Perfectly!!
We have successfully implemented JWT Authentication in NodeJS!
Now you can easily follow these step to authenticate your NodeJS application!
For More references You can check the following resources as well
JWT.io: https://jwt.io/
Microsoft Docs: https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens
Auth0.com: https://auth0.com/learn/json-web-tokens
If you found this blog post helpful, please consider sharing it with others who might benefit. You can also follow me for more content on Javascript, React, and other web Development topics.
To sponsor my work, please visit: Arindam's Sponsor Page and explore the various sponsorship options.
Connect with me on Twitter, LinkedIn, Youtube and GitHub.
Thank you for Reading : )
Also published on DEV.to.