A Complete Guide to Node.js, Express.js, and JWT Authentication

Welcome to our comprehensive guide on implementing authentication using Node.js, Express.js, and JWT (JSON Web Tokens). In this blog, we'll dive into the world of secure authentication for your web applications and APIs. Whether you're a pro backend developer looking to brush up on JWT authentication or a beginner eager to explore the topic, this blog has got you covered!
What is JWT Authentication?
JWT (JSON Web Token) is a compact, URL-safe means of representing claims to be transferred between two parties securely. It is commonly used to authenticate users and authorize access to protected resources in web applications and APIs. The token is digitally signed, ensuring its integrity, and can contain user-specific information (payload) like user ID, roles, and expiration time.
Setting Up a Node.js and Express.js Project
To get started, let's create a new Node.js project and set up the basic Express.js server. Make sure you have Node.js and npm (Node Package Manager) installed. Create a new project folder and run the following commands in the terminal:
mkdir jwt-authentication-example
cd jwt-authentication-example
npm init -y
npm install express --save
Installing Dependencies
In addition to Express.js, we'll need a few more packages to handle user authentication and JWT. Install the required dependencies using npm:
npm install body-parser bcrypt jsonwebtoken
Creating a User Model and Database
Let's create a user
model to represent our application's users. We'll be using MongoDB as our database, but you can choose any database of your choice:
// models/User.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
});
module.exports = mongoose.model('User', userSchema);
User Registration and Password Hashing
Implement user registration and password hashing using bcrypt. Bcrypt helps us securely store passwords in the database by hashing them.
// routes/auth.js
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const User = require('../models/User');
router.post('/register', async (req, res) => {
try {
const { username, password, role } = req.body;
role = role || 'user';
const hashedPassword = await bcrypt.hash(password, 10);
const user = new User({ username, password: hashedPassword });
await user.save();
res.status(201).json({ message: 'User registered successfully' });
}
catch (error) {
res.status(500).json({ error: 'An error occurred' });
}
});
Implementing User Login with Token Generation
Let's create a route for user login. We'll compare the provided password with the hashed password in the database. Generate JWT token using secret key. Refer to our article to generate cryptographic secure key.
// routes/auth.js
const jwt = require('jsonwebtoken');
const generateToken = (user) => {
return jwt.sign (
{
userId: user._id,
username: user.username,
role: user.role
},
'your_secret_key_here',
{ expiresIn: '1h' }
);
};
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate JWT
const token = generateToken(user);
res.status(200).json({ token: token });
}
catch (error) {
res.status(500).json({ error: 'An error occurred' });
}
});
Understanding JWT Payload and Token Signing
Imagine we want to login from our application through the browsers, well first of all users logs in via webform from our website which sends a request to the server with the credentials, an email and the password, the server than check the those email and password against those stored in the database, if they are correct the server then creates Json web tokens for the user and sends it to the browser where it can be stored in the cookie or local storage. The JWT token contains encoded data that about user to identify them, so as long as they have token in the cookie they are considered logged in and authenticated. But still Browser / Client has to send JWT token as part of authorization header to access any protected resources.
JWT is basically an encoded long string of characters which has basically 3 parts Header, Payload, and Signature.
The Header contains metadata, the Payload holds user-specific data, and the Signature is used to verify the token's authenticity. The server signs the token using a secret key and sends it to the client upon successful login.
After successfully logging in, the server will generate a JSON Web Token (JWT) and send it back to the client as part of the response. The JWT will be included in the token field of the response body. Here's a sample response after a successful login:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYiLCJ1c2VybmFtZSI6ImRvZV9qb2UiLCJyb2xlIjoidXNlciIsImlhdCI6MTYzMDAwMDAwMCwiZXhwIjoxNjMwMDAwMDAwfQ.7V_M1yNlFEv8wV7SZat20Bcvk_iwuj1skge4Q5R76ts"
}
In this example, the JWT is a long string containing three segments separated by dots:
Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
(Base64URL-encoded JSON data)
Payload: eyJ1c2VySWQiOiIxMjM0NTYiLCJ1c2VybmFtZSI6ImRvZV9qb2UiLCJyb2xlIjoidXNlciIsImlhdCI6MTYzMDAwMDAwMCwiZXhwIjoxNjMwMDAwMDAwfQ
(Base64URL-encoded JSON data)
Signature: 7V_M1yNlFEv8wV7SZat20Bcvk_iwuj1skge4Q5R76ts
(A signature used to verify the token's authenticity)
The payload contains user-specific data, such as userId
, username
, role
, and token expiration time
(iat and exp). The token is signed with a secret key (not shown in the response) known only to the server. The client should securely store the token (usually in local storage or a cookie) and include it in the Authorization header of subsequent requests to protected routes for authentication and access authorization.
Protecting Routes with JWT Authentication and User Route
Now, let's protect routes based on user roles using the verifyToken middleware.
// index.js (main server file)
const express = require('express');
const app = express();
const authRoutes = require('./routes/auth');
const verifyToken = require('./middleware/verifyToken');
// Parse JSON requests
app.use(bodyParser.json());
// Authentication routes
app.use('/auth', authRoutes);
// Route accessible to all authenticated users
app.get('/users/me', verifyToken, (req, res) => {
const user = req.user;
res.json( {
username: user.username,
role: user.role
} );
});
// Route accessible only to admins
app.get('/admin/resource', verifyToken, (req, res) => {
const user = req.user;
if (user.role !== 'admin') {
return res.sendStatus(403);
}
res.json({ message: 'You have access to this protected route' });
});
Conclusion
In this blog, we have explored the fundamentals of JWT authentication with Node.js and Express.js. You now have a solid understanding of creating a secure authentication system for your web applications and APIs using JSON Web Tokens. The few things we covered in this article were hashing of passwords, creating JSON web tokens, verification using jwt, and protecting routes using jwt.
Remember to always handle sensitive information securely, use strong and unique secret keys for signing tokens, and keep your authentication system up-to-date with the latest security practices.
Source code available in Github