How to Implement JWT Authentication in Node.js

This article provides a straightforward, step-by-step guide on implementing JSON Web Token (JWT) authentication in a Node.js API. You will learn how to secure your endpoints by setting up a basic Express server, hashing passwords with bcryptjs, generating JWTs upon successful user login, and creating a middleware function to authorize requests to protected routes.

Step 1: Install the Required Dependencies

To get started, you need to initialize a Node.js project and install the necessary packages. Run the following commands in your terminal:

npm init -y
npm install express jsonwebtoken bcryptjs dotenv

Step 2: Configure Environment Variables

Create a file named .env in the root of your project to store your secret key and port configuration.

PORT=3000
JWT_SECRET=your_super_secret_key_change_this_in_production

Step 3: Set Up the Express Server and Mock Database

Create an app.js file. For simplicity, this guide uses an in-memory array to mock a user database.

require('dotenv').config();
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const app = express();
app.use(express.json());

const users = []; // Mock database

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Step 4: Implement User Registration

Before a user can log in, they need to register. You must hash their password using bcryptjs before saving it to the database.

app.post('/register', async (req, res) => {
    try {
        const { username, password } = req.body;
        if (!username || !password) {
            return res.status(400).json({ message: 'Username and password are required' });
        }

        // Hash the password
        const hashedPassword = await bcrypt.hash(password, 10);
        
        const newUser = { username, password: hashedPassword };
        users.push(newUser);

        res.status(201).json({ message: 'User registered successfully' });
    } catch (error) {
        res.status(500).json({ message: 'Error registering user' });
    }
});

Step 5: Implement User Login and JWT Generation

When a user logs in with valid credentials, the API generates a JWT. This token contains a payload (like the user’s identifier) and is signed using the secret key.

app.post('/login', async (req, res) => {
    const { username, password } = req.body;
    const user = users.find(u => u.username === username);

    if (!user) {
        return res.status(400).json({ message: 'Invalid credentials' });
    }

    // Verify password
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
        return res.status(400).json({ message: 'Invalid credentials' });
    }

    // Generate JWT
    const token = jwt.sign(
        { username: user.username },
        process.env.JWT_SECRET,
        { expiresIn: '1h' }
    );

    res.json({ token });
});

Step 6: Create the Authentication Middleware

To protect routes, create a middleware function that extracts the JWT from the Authorization header, verifies its validity, and attaches the decoded payload to the request object.

const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // Format: "Bearer TOKEN"

    if (!token) {
        return res.status(401).json({ message: 'Access denied. No token provided.' });
    }

    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
        if (err) {
            return res.status(403).json({ message: 'Invalid or expired token.' });
        }
        req.user = user;
        next();
    });
};

Step 7: Protect Private Routes

Apply the authenticateToken middleware to any API endpoint that requires authentication.

app.get('/dashboard', authenticateToken, (req, res) => {
    res.json({ message: `Welcome to your private dashboard, ${req.user.username}!` });
});

To test your implementation, first register a user at /register, log in at /login to receive your JWT, and then make a GET request to /dashboard by passing the token in the headers as Authorization: Bearer <your_token>.