DEV Community

Cover image for Building a Scalable Authentication System with JWT in a MERN Stack Application
abhilaksh-arora
abhilaksh-arora

Posted on

2

Building a Scalable Authentication System with JWT in a MERN Stack Application

Introduction:

Authentication is a fundamental aspect of web development, ensuring that users can securely access protected resources. JSON Web Tokens (JWT) have become a popular choice for implementing authentication in modern web applications due to their simplicity and scalability. In this tutorial, we'll explore how to implement a scalable authentication system using JWT in a MERN (MongoDB, Express.js, React, Node.js) stack application. We'll cover user registration, login, token generation, and secure authorization.

Prerequisites:

Before we begin, make sure you have the following prerequisites installed:

  • Node.js and npm (Node Package Manager)
  • MongoDB (you can use a local or remote instance)
  • React.js (if you're building a frontend)

Setting Up the Backend:

  1. Initialize a New Node.js Project:

Create a new directory for your project and initialize a new Node.js project by running the following commands:

   mkdir mern-authentication
   cd mern-authentication
   npm init -y
Enter fullscreen mode Exit fullscreen mode
  1. Install Required Packages:

Install the necessary packages for our backend using the following command:

   npm install express mongoose jsonwebtoken bcryptjs body-parser cors
Enter fullscreen mode Exit fullscreen mode
  • express: Web framework for Node.js.
  • mongoose: MongoDB object modeling tool.
  • jsonwebtoken: Library for generating JWT tokens.
  • bcryptjs: Library for hashing passwords securely.
  • body-parser: Middleware for parsing request bodies.
  • cors: Middleware for enabling Cross-Origin Resource Sharing.
  1. Create a MongoDB Database:

Set up a MongoDB database either locally or using a cloud service like MongoDB Atlas. Note down the connection URI.

  1. Create the Backend Server:

Create a file named server.js and set up a basic Express server with MongoDB connection:

   // server.js

   const express = require('express');
   const mongoose = require('mongoose');
   const bodyParser = require('body-parser');
   const cors = require('cors');

   const app = express();
   const PORT = process.env.PORT || 5000;

   // Middleware
   app.use(bodyParser.json());
   app.use(cors());

   // Connect to MongoDB
   mongoose.connect('mongodb://localhost:27017/mern_auth', {
     useNewUrlParser: true,
     useUnifiedTopology: true,
     useCreateIndex: true,
   })
   .then(() => console.log('MongoDB connected'))
   .catch(err => console.log(err));

   // Routes
   app.use('/api/auth', require('./routes/auth'));

   // Start the server
   app.listen(PORT, () => {
     console.log(`Server running on port ${PORT}`);
   });
Enter fullscreen mode Exit fullscreen mode
  1. Create Routes for Authentication:

Create a folder named routes and add a file named auth.js inside it. This file will contain routes for user registration, login, and token verification.

   // routes/auth.js

   const express = require('express');
   const router = express.Router();
   const bcrypt = require('bcryptjs');
   const jwt = require('jsonwebtoken');
   const User = require('../models/User');
   const { check, validationResult } = require('express-validator');

   // Register a new user
   router.post('/register', [
     check('name', 'Please enter a name').not().isEmpty(),
     check('email', 'Please include a valid email').isEmail(),
     check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 })
   ], async (req, res) => {
     // Validation errors
     const errors = validationResult(req);
     if (!errors.isEmpty()) {
       return res.status(400).json({ errors: errors.array() });
     }

     const { name, email, password } = req.body;

     try {
       // Check if user already exists
       let user = await User.findOne({ email });
       if (user) {
         return res.status(400).json({ msg: 'User already exists' });
       }

       // Create new user
       user = new User({ name, email, password });

       // Hash password
       const salt = await bcrypt.genSalt(10);
       user.password = await bcrypt.hash(password, salt);

       // Save user to database
       await user.save();

       // Generate JWT token
       const payload = {
         user: { id: user.id }
       };
       jwt.sign(payload, 'jwtSecret', { expiresIn: 3600 }, (err, token) => {
         if (err) throw err;
         res.json({ token });
       });

     } catch (err) {
       console.error(err.message);
       res.status(500).send('Server Error');
     }
   });

   // Login route
   router.post('/login', [
     check('email', 'Please include a valid email').isEmail(),
     check('password', 'Password is required').exists()
   ], async (req, res) => {
     // Validation errors
     const errors = validationResult(req);
     if (!errors.isEmpty()) {
       return res.status(400).json({ errors: errors.array() });
     }

     const { email, password } = req.body;

     try {
       // Check if user exists
       let user = await User.findOne({ email });
       if (!user) {
         return res.status(400).json({ msg: 'Invalid credentials' });
       }

       // Compare passwords
       const isMatch = await bcrypt.compare(password, user.password);
       if (!isMatch) {
         return res.status(400).json({ msg: 'Invalid credentials' });
       }

       // Generate JWT token
       const payload = {
         user: { id: user.id }
       };
       jwt.sign(payload, 'jwtSecret', { expiresIn: 3600 }, (err, token) => {
         if (err) throw err;
         res.json({ token });
       });

     } catch (err) {
       console.error(err.message);
       res.status(500).send('Server Error');
     }
   });

   module.exports = router;
Enter fullscreen mode Exit fullscreen mode
  1. Create a User Model:

Create a folder named models and add a file named User.js inside it. This file will define the user schema and model.

   // models/User.js

   const mongoose = require('mongoose');

   const UserSchema = new mongoose.Schema({
     name: {
       type: String,
       required: true
     },
     email: {
       type: String,
       required: true,
       unique: true
     },
     password: {
       type: String,
       required: true
     },
     date: {
       type: Date,
       default: Date.now
     }
   });

   module.exports = User = mongoose.model('user', UserSchema);
Enter fullscreen mode Exit fullscreen mode

Conclusion:

In this tutorial, we've implemented a scalable authentication system using JSON Web Tokens (JWT) in a MERN stack application. We've covered user registration, login, token generation, and secure authorization. This authentication system provides a solid foundation for building secure and scalable web applications. Feel free to extend this implementation by adding features like password reset, email verification, and role-based access control.

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up