DEV Community

Cover image for Mastering Authentication in Node.js and Express: A Comprehensive Guide
Siddhant
Siddhant

Posted on

Mastering Authentication in Node.js and Express: A Comprehensive Guide

Authentication is a crucial component in modern web applications, allowing developers to ensure that only authorized users can access specific features or data. In a Node.js and Express-based application, authentication is typically handled using tokens, most commonly JSON Web Tokens (JWT), due to their efficiency and security.

In this guide, we'll explore how to set up user authentication in a Node.js and Express application using JWTs. By the end, you'll have a solid understanding of how to implement secure authentication for your own projects.

What is Authentication?

Authentication is the process of verifying the identity of a user or system. In web applications, it involves checking user credentials like a username and password. Upon successful verification, the system allows the user to interact with the application. For enhanced security, token-based authentication such as JWTs are commonly used.

Why Use JSON Web Tokens (JWT)?

JWTs are an industry standard (RFC 7519) for secure and stateless token-based authentication. They allow information to be transmitted securely between parties as a JSON object. Tokens are often used to verify a user's identity without needing to store session data on the server, making JWT a great choice for stateless applications.

Step-by-Step: Implementing Authentication in Node.js and Express

Let’s break down the implementation of a basic authentication system using JWT in Node.js and Express.

1. Setting Up Your Node.js Application

Before diving into authentication, we need to set up a basic Node.js and Express application. Follow these steps to initialize your project:

mkdir auth-demo
cd auth-demo
npm init -y
npm install express bcryptjs jsonwebtoken mongoose dotenv
Enter fullscreen mode Exit fullscreen mode

Here’s what each dependency is for:

  • express: Framework for building our Node.js server.
  • bcryptjs: To hash and compare passwords securely.
  • jsonwebtoken: For generating and verifying JWTs.
  • mongoose: To interact with MongoDB.
  • dotenv: For managing environment variables like secrets and database connection strings.

2. Configuring Environment Variables

Create a .env file in the root directory of your project to store sensitive information like your database URI and JWT secret key.

MONGODB_URI=mongodb://localhost:27017/auth-demo
JWT_SECRET=your_jwt_secret_key
Enter fullscreen mode Exit fullscreen mode

3. Connecting to MongoDB

In the project’s root, create a db.js file inside a config folder to handle the MongoDB connection.

// config/db.js
const mongoose = require('mongoose');
const dotenv = require('dotenv');

dotenv.config();

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log('MongoDB connected');
  } catch (err) {
    console.error('Error connecting to MongoDB:', err.message);
    process.exit(1);
  }
};

module.exports = connectDB;
Enter fullscreen mode Exit fullscreen mode

4. Creating the User Model

Next, create a User model to define the structure of user documents in MongoDB. Inside a models folder, create User.js:

// models/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);
Enter fullscreen mode Exit fullscreen mode

5. Implementing User Registration

We’ll now set up the route for user registration. In the controllers folder, create a file called authController.js and implement the registration logic.

// controllers/authController.js
const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

// User registration
exports.register = async (req, res) => {
  const { username, password } = req.body;

  try {
    const existingUser = await User.findOne({ username });
    if (existingUser) {
      return res.status(400).json({ message: 'Username already exists' });
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = new User({ username, password: hashedPassword });

    await newUser.save();
    res.status(201).json({ message: 'User registered successfully' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};
Enter fullscreen mode Exit fullscreen mode

This logic hashes the password using bcrypt before storing the user’s information in MongoDB.

6. Implementing User Login

Login is crucial for generating and returning JWTs, which the client will use to authenticate future requests. Here’s how to implement the login logic:

// controllers/authController.js (continue)
exports.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' });
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(401).json({ message: 'Invalid username or password' });
    }

    const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
    res.json({ token });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
};
Enter fullscreen mode Exit fullscreen mode

If the login is successful, we generate a JWT using jsonwebtoken and send it to the client.

7. Setting Up Middleware for Protected Routes

JWTs are useful for protecting routes that require authentication. We’ll create middleware to verify the token and ensure only authorized users can access specific endpoints.

// middleware/authMiddleware.js
const jwt = require('jsonwebtoken');

exports.verifyToken = (req, res, next) => {
  const token = req.headers['authorization'];
  if (!token) return res.sendStatus(403);

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
};
Enter fullscreen mode Exit fullscreen mode

8. Applying Authentication Middleware

Finally, let’s apply the middleware to protect a route. For example, you might want users to access their profile only after logging in:

// routes/userRoutes.js
const express = require('express');
const { verifyToken } = require('../middleware/authMiddleware');
const { getUserProfile } = require('../controllers/userController');

const router = express.Router();

router.get('/profile', verifyToken, getUserProfile);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

The verifyToken middleware checks for a valid JWT in the request’s headers and allows access to the route if the token is verified.

Conclusion

In this guide, we covered the essentials of implementing user authentication using JWT in a Node.js and Express application. We walked through setting up user registration, login, and protecting routes using token-based authentication. With this foundation, you can build robust, secure authentication systems in your own applications. As you continue to develop, consider adding refresh tokens, password reset functionality, and multi-factor authentication for enhanced security.

By mastering authentication with Node.js and Express, you're well on your way to building scalable, secure web applications.

Top comments (0)