Secure Node.js APIs with JWT Authentication

Learn how to secure Node.js and Express APIs using JWT authentication. This guide explains bcrypt password hashing, token generation, middleware protection, and stateless authentication architecture.

Author: hamza ougjjou
Published: May 22, 2026
Reading time: 4 min read
Secure Node.js APIs with JWT Authentication

Secure Node.js APIs with JWT Authentication

Modern REST APIs cannot remain completely public and unprotected.

Applications need authentication systems to identify users and authorize secure actions like creating posts, updating data, or deleting resources.

The most popular solution for stateless authentication in modern APIs is JWT authentication.

What is JWT?

JWT stands for JSON Web Token.

It is a compact token format used to securely transmit user identity information between the client and the server.

Instead of storing sessions on the server, JWT keeps authentication stateless.

Why Stateless Authentication Matters

Traditional session-based authentication stores user sessions directly on the server.

This creates scalability limitations because servers must remember every connected user.

JWT solves this problem by storing authentication data inside signed tokens.

How JWT Works

  1. User logs in using email and password
  2. Server validates credentials
  3. Server generates a signed JWT token
  4. Client stores the token
  5. Client sends the token with every protected request

The server verifies the token signature without storing session data.

JWT Structure

A JWT token contains three parts:

  • Header
  • Payload
  • Signature

The payload usually contains user information such as:


{
    id: user._id,
    isAdmin: false
}

The signature protects the token from tampering.

Installing Required Packages


npm install bcryptjs jsonwebtoken

These packages provide:

  • bcryptjs → Password hashing
  • jsonwebtoken → JWT generation and verification

Password Hashing with bcrypt

Passwords should never be stored as plain text.

bcrypt securely hashes passwords before storing them in the database.


const salt =
    await bcrypt.genSalt(10);

const hashedPassword =
    await bcrypt.hash(
        req.body.password,
        salt
    );

Even if the database leaks, original passwords remain protected.

User Registration


router.post('/register',
async (req, res) => {

    const hashedPassword =
        await bcrypt.hash(
            req.body.password,
            10
        );

    const user = new User({
        username: req.body.username,
        email: req.body.email,
        password: hashedPassword
    });

    await user.save();

    res.status(201).json(user);
});

Creating JWT Tokens

After successful login, the server generates a JWT token.


const token = jwt.sign(

    {
        id: user._id
    },

    process.env.JWT_SECRET,

    {
        expiresIn: '3d'
    }
);

The secret key signs the token and prevents forgery.

User Login


router.post('/login',
async (req, res) => {

    const user =
        await User.findOne({
            email: req.body.email
        });

    const validPassword =
        await bcrypt.compare(
            req.body.password,
            user.password
        );

    if (!validPassword) {
        return res.status(400)
            .json('Wrong password');
    }

    const token = jwt.sign(
        { id: user._id },
        process.env.JWT_SECRET
    );

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

Protecting Routes with Middleware

Protected routes require token verification middleware.


function verifyToken(
    req,
    res,
    next
) {

    const authHeader =
        req.headers.authorization;

    if (!authHeader) {
        return res.status(401)
            .json('Unauthorized');
    }

    const token =
        authHeader.split(' ')[1];

    jwt.verify(
        token,
        process.env.JWT_SECRET,
        (err, user) => {

            if (err) {
                return res.status(403)
                    .json('Invalid token');
            }

            req.user = user;

            next();
        }
    );
}

Using Protected Routes


router.post(
    '/posts',
    verifyToken,
    async (req, res) => {

        const post =
            new Post({
                title: req.body.title,
                author: req.user.id
            });

        await post.save();

        res.json(post);
    }
);

Only authenticated users can now access this route.

Best Practices

  • Never store plain passwords
  • Use strong JWT secret keys
  • Set token expiration times
  • Use HTTPS in production
  • Validate incoming data
  • Protect sensitive routes

Conclusion

JWT authentication is one of the most important security systems in modern backend development.

By combining bcrypt password hashing with JWT verification middleware, developers can build scalable and secure Node.js APIs without relying on traditional server sessions.

Advertisement

Comments

No Comments Yet

Be the first to leave a comment.

Related Articles