DEV Community

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

Posted on

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)