Security is the backbone of any modern web application. You want to ensure, when you build an app, that only the right people access certain data. So, now we’re going to talk about JWT Authentication in Node.js. This is the standard way to handle user sessions safely and efficiently without overloading your database.
In this tutorial, we will be working on a full authentication system. We’ll be creating anything from setting up the project to protecting routes. Think of this as building a digital VIP club where only users with a valid pass get inside. Let’s dive in and secure your application.
What is JWT Authentication?
JSON Web Token (JWT) works similarly to a digital wristband you get at a music festival. The security guard checks your ID at the gate (login) and gives you a wristband. You show that wristband every time you want to enter different stages or buy drinks. You won’t have to show your ID card each time.
Similarly, in web development, once the server verifies user credentials, it gives a token in return. This token is stored at the client-side and passed along with every subsequent request. This way, the server can identify the user without checking the database for username and password repeatedly.
Setting Up the Node.js Application
We need to initialize our project and install the necessary tools. Open your terminal and create a new folder for your project. Run the initialization command to create a package.json file.
npm init -y
It will take some time to install project. Now, we will install the dependencies required to build a nodejs jwt token authentication example.
npm install express bcryptjs jsonwebtoken mongoose dotenv express-async-handler
Here is a quick breakdown of what these packages do:
- express: Runs our web server and handles API routing.
- bcryptjs: Encrypts user passwords so we never store plain text in the database.
- jsonwebtoken: Generates and verifies the access tokens.
- mongoose: Helps us interact with our MongoDB database.
- dotenv: Loads secret keys and configuration variables from a file.
- express-async-handler: It acts as a wrapper for your async functions. It catches any errors inside your routes and passes them to your Express error handler automatically.
Database and User Schema Setup
We need a place to store user data. First, create a .env file in your root directory. This file keeps sensitive information safe. Add your MongoDB connection string and a secret key for signing tokens.
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_super_secret_key
PORT=5000
Next, create a file named db.js to handle the connection. This code connects our application to the database using Mongoose.
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGO_URI);
console.log(`MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
console.log(error);
process.exit(1);
}
};
module.exports = connectDB;
Next thing, we need to do is create an model schema for our User. it will work as blueprint for user model. Let’s create userModel.js inside models directory.
const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
name: {
type: String,
required: [true, 'Please add a name']
},
email: {
type: String,
required: [true, 'Please add an email'],
unique: true
},
password: {
type: String,
required: [true, 'Please add a password']
}
}, {
timestamps: true
});
module.exports = mongoose.model('User', userSchema);
Authentication APIs using JWT
Next, we will create the logic to handle user registration, login, and logout APIs. It is the core of how to implement jwt in node js express application. Create a file named authController.js inside a controllers folder.
Register API
The registration process involves taking user input, checking if the user already exists, and hashing the password. We use bcryptjs to scramble the password into a secure string. Finally, we save the user to the database.
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const asyncHandler = require('express-async-handler');
const User = require('../models/userModel');
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: '30d',
});
};
const registerUser = asyncHandler(async (req, res) => {
const { name, email, password } = req.body;
if (!name || !email || !password) {
res.status(400);
throw new Error('Please add all fields');
}
const userExists = await User.findOne({ email });
if (userExists) {
res.status(400);
throw new Error('User already exists');
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
const user = await User.create({
name,
email,
password: hashedPassword
});
if (user) {
res.status(201).json({
_id: user.id,
name: user.name,
email: user.email,
token: generateToken(user._id)
});
} else {
res.status(400);
throw new Error('Invalid user data');
}
});
First, we will validate API data for empty fields. If everything looks good, we encrypt the password and create the user. Notice that we return a token immediately so the user logs in right after signing up.
Login Endpoint
The login function validates the user’s credentials. We find the user by email and compare the entered password with the hashed password stored in the database. If they match, we issue a token.
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
res.json({
_id: user.id,
name: user.name,
email: user.email,
token: generateToken(user._id)
});
} else {
res.status(400);
throw new Error('Invalid credentials');
}
});
This section creates the nodejs login authentication token using jwt. The generateToken function uses our secret key to sign the payload. This token acts as the user’s proof of identity for the next 30 days.
Logout Endpoint
JWT is stateless, meaning the server does not keep a record of active tokens. Logout usually happens on the client side (frontend) by simply removing the token from local storage. However, we can define an endpoint to send a confirmation response.
const logoutUser = (req, res) => {
res.status(200).json({ message: 'User logged out successfully' });
};
module.exports = {
registerUser,
loginUser,
logoutUser,
};
Here, we are just providing response with just message. However you can perform blacklist method for advance where system will blacklist user token on logout.
Creating Middleware for Secure Routes
Now our basic authentication setup is complete, we can move forward with implementation of protecting private routes. This middleware acts like the bouncer at the club. It checks the request header for a token. If the token exists and is valid, it allows the request to proceed. If not, it blocks access.
Let’s create a folder named middleware and add authMiddleware.js.
const jwt = require('jsonwebtoken');
const asyncHandler = require('express-async-handler');
const User = require('../models/userModel');
const protect = asyncHandler(async (req, res, next) => {
let token;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
try {
token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
next();
} catch (error) {
console.log(error);
res.status(401);
throw new Error('Not authorized');
}
}
if (!token) {
res.status(401);
throw new Error('Not authorized, no token');
}
});
module.exports = { protect };
This middleware extracts the token from the “Bearer” string. It verifies the signature and attaches the user data to the request object. This allows you to secure nodejs api with json web token effectively. Now, let’s setup a simple route file userRoutes.js to connect everything.
const express = require('express');
const router = express.Router();
const { registerUser, loginUser, logoutUser } = require('../controllers/authController');
const { protect } = require('../middleware/authMiddleware');
router.post('/', registerUser);
router.post('/login', loginUser);
router.post('/logout', logoutUser);
router.get('/me', protect, (req, res) => {
res.status(200).json(req.user);
});
module.exports = router;
Testing JWT Authentication Functionality
It is time to bring our application to life. Set up your main entry file server.js to run the server.
const express = require('express');
const dotenv = require('dotenv').config();
const connectDB = require('./config/db');
const port = process.env.PORT || 5000;
connectDB();
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/api/users', require('./routes/userRoutes'));
app.listen(port, () => console.log(`Server started on port ${port}`));
Here, we get together all functionality into single entry point like database models, controller, routes and middleware. Run the server using node server.js. You can use postman for API testing or create frontend.
Conclusion
You have successfully built a secure authentication system. We have discussed how to set up the database, create user models, and handle tokens. Understanding JWT Authentication in Node.js gives you the power to build scalable and secure applications. You can now expand this project by adding role-based access or password reset features.
