Using JWT for Authentication in a MERN Backend

Surajit DasSurajit Das
4 min read

What is JWT and Why Use It?

JWT (JSON Web Token) is an open standard used to securely transmit information between parties as a JSON object. It is commonly used for authentication and authorization.

  • Authentication: Verifies who the user is.

  • Authorization: Verifies what the user has access to.

In a MERN stack (MongoDB, Express.js, React, Node.js), JWT is typically used to:

  • Log in the user and issue a token.

  • Store the token on the client (usually in localStorage or cookies).

  • Send the token in headers for protected API routes.

  • Validate the token on the server to allow/deny access.

Project Structure

backend/
│
├── config/
|    └── db.js
├── controllers/
│   └── user.controller.js
├── middleware/
│   └── auth.middleware.js
├── models/
│   └── user.model.js
├── routes/
│   └── user.routes.js
├── index.js
└── .env

Setup & Dependencies

Install the necessary packages:

npm install express mongoose dotenv jsonwebtoken bcryptjs

.env

PORT=5000
MONGO_URI=your_mongodb_url
JWT_SECRET=your_jwt_secret_key

config/db.js

const mongoose = require('mongoose');
require('dotenv').config();

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI);
    console.log('MongoDB Connected');
  } catch (error) {
    console.error('MongoDB connection failed:', error.message);
    process.exit(1);
  }
};

module.exports = connectDB;

models/user.model.js

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  mobile: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
}, { timestamps: true });

module.exports = mongoose.model('User', userSchema);

middleware/auth.middleware.js

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

const protect = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1]; // Bearer TOKEN

  if (!token) {
    return res.status(401).json({ message: 'Not authorized, token missing' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // Contains userId and email from payload
    next();
  } catch (error) {
    return res.status(401).json({ message: 'Invalid token' });
  }
};

module.exports = protect;

controllers/user.controller.js

const User = require('../models/user.model');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
require('dotenv').config();

// Generate JWT Token
const generateToken = (user) => {
  return jwt.sign(
    { userId: user._id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn: '1d' }
  );
};

// @route   POST /api/users/register
const registerUser = async (req, res) => {
  const { name, mobile, email, password } = req.body;

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

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

    const user = await User.create({ name, mobile, email, password: hashedPassword });

    const token = generateToken(user);
    res.status(201).json({ userId: user._id, email: user.email, token });
  } catch (error) {
    res.status(500).json({ message: 'Server error' });
  }
};

// @route   POST /api/users/login
const loginUser = async (req, res) => {
  const { email, password } = req.body;

  try {
    const user = await User.findOne({ email });
    if (!user) return res.status(400).json({ message: 'Invalid credentials' });

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

    const token = generateToken(user);
    res.status(200).json({ userId: user._id, email: user.email, token });
  } catch (error) {
    res.status(500).json({ message: 'Server error' });
  }
};

// @route   GET /api/users/profile
const getUserProfile = async (req, res) => {
  try {
    const user = await User.findById(req.user.userId).select('-password');
    res.json(user);
  } catch (error) {
    res.status(500).json({ message: 'Failed to retrieve profile' });
  }
};

module.exports = {
  registerUser,
  loginUser,
  getUserProfile,
};

routes/user.routes.js

const express = require('express');
const { registerUser, loginUser, getUserProfile } = require('../controllers/user.controller');
const protect = require('../middleware/auth.middleware');

const router = express.Router();

router.post('/register', registerUser);
router.post('/login', loginUser);
router.get('/profile', protect, getUserProfile);

module.exports = router;

index.js

const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');
const userRoutes = require('./routes/user.routes');

dotenv.config();
connectDB();

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

app.use('/api/users', userRoutes);

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

Testing the Flow

Use Postman or Insomnia:

  1. POST /api/users/register → Register new user.

  2. POST /api/users/login → Get JWT token.

  3. GET /api/users/profile → Use Authorization: Bearer <token> header.

JWT provides a secure and stateless way to authenticate users in your MERN backend. By separating concerns into models, controllers, routes, and middleware, your project remains clean, scalable, and easy to maintain.

0
Subscribe to my newsletter

Read articles from Surajit Das directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Surajit Das
Surajit Das