Build a Secure Login System with Express.js and MongoDB — Featuring Roles, Permissions, and Dual Dashboards

Author

Kritim Yantra

Aug 04, 2025

Build a Secure Login System with Express.js and MongoDB — Featuring Roles, Permissions, and Dual Dashboards

Ever built a login page… only to realize you have no idea where to go from there?

We've all been there. You just got your shiny "Login" and "Register" buttons working on your first Node.js app. But suddenly, you’re hit with questions like:

"How do I separate the admin dashboard from normal users?"
"Where do I store roles and permissions?"
"Wait… how do I even organize my files properly?"

Trust me, I learned this the hard way — with messy folders, spaghetti code, and lots of 401 errors. 😩

So, let’s do this right.

In this post, I’ll walk you through building a solid Express.js + MongoDB login/register system, complete with:

  • 📂 A clean folder structure
  • 🧑💼 Two login systems (Admin and User)
  • 🔐 Role-based access control (RBAC)
  • 🧭 Two separate dashboards
  • ️ A centralized admin panel and public-facing website

Let’s dive in step-by-step — no jargon, no overwhelm.


🧱 Folder Structure That Actually Makes Sense

Before we write any code, let’s organize our project like a pro:

project-root/
│
├── controllers/
│   ├── authController.js
│   ├── adminController.js
│   └── userController.js
│
├── middlewares/
│   └── authMiddleware.js
│
├── models/
│   ├── User.js
│   └── Role.js
│
├── routes/
│   ├── authRoutes.js
│   ├── adminRoutes.js
│   └── userRoutes.js
│
├── utils/
│   └── token.js
│
├── config/
│   └── db.js
│
├── app.js
└── server.js

📌 Pro Tip: A clean structure saves your future self hours of frustration when debugging or adding features.


🧑💻 Step-by-Step: Login, Register, Roles & Permissions

Let’s build it in layers. Follow along 👇


🔗 1. Connect to MongoDB

config/db.js

const mongoose = require('mongoose');

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

module.exports = connectDB;

👤 2. User and Role Models

models/User.js

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: String,
  email: { type: String, unique: true },
  password: String,
  role: { type: String, enum: ['admin', 'user'], default: 'user' }
});

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

models/Role.js (Optional: for custom permissions)

const mongoose = require('mongoose');

const roleSchema = new mongoose.Schema({
  role: String,
  permissions: [String]
});

module.exports = mongoose.model('Role', roleSchema);

🛂 3. Auth Controller: Register & Login

controllers/authController.js

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

exports.register = async (req, res) => {
  const { name, email, password, role } = req.body;
  const hashed = await bcrypt.hash(password, 10);
  const user = new User({ name, email, password: hashed, role });
  await user.save();
  res.status(201).json({ message: 'User registered' });
};

exports.login = async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });
  if (!user || !await bcrypt.compare(password, user.password)) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET);
  res.json({ token, role: user.role });
};

🔐 4. Middleware: Role-Based Auth

middlewares/authMiddleware.js

const jwt = require('jsonwebtoken');

exports.protect = (roles = []) => {
  return (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) return res.sendStatus(401);

    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      if (!roles.includes(decoded.role)) return res.sendStatus(403);
      req.user = decoded;
      next();
    } catch {
      res.sendStatus(401);
    }
  };
};

🚦 5. Routes for Auth and Dashboards

routes/authRoutes.js

const express = require('express');
const router = express.Router();
const { register, login } = require('../controllers/authController');

router.post('/register', register);
router.post('/login', login);

module.exports = router;

routes/adminRoutes.js

const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/authMiddleware');

router.get('/dashboard', protect(['admin']), (req, res) => {
  res.json({ message: 'Welcome to the Admin Dashboard' });
});

module.exports = router;

routes/userRoutes.js

const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/authMiddleware');

router.get('/dashboard', protect(['user']), (req, res) => {
  res.json({ message: 'Welcome to the User Dashboard' });
});

module.exports = router;

🖥️ Admin Panel vs. Website

You can keep the admin panel under a subdomain like admin.myapp.com, and the regular user website as www.myapp.com.

Your front-end just needs to hit the respective dashboards:

  • Admin: GET /admin/dashboard
  • User: GET /user/dashboard

You control what the user sees by checking their role from the decoded JWT.


✅ Wrapping Up: Why This Structure Rocks

  • ✅ Easy to maintain and scale
  • ✅ Secure, role-based access
  • ✅ Two dashboards = clear separation of concerns
  • ✅ Cleaner code = happier dev

You’re not just building a login system — you’re building a real-world, production-ready system.


🙋️ Beginner FAQ

Q1: Why separate dashboards for admin and user?
Because admins usually need tools like user management, analytics, etc., which regular users don’t need to see.

Q2: Can I store more roles?
Yes! Add roles like moderator, editor, etc., and update the middleware checks.

Q3: What if I need custom permissions?
Use a separate Role model with permission arrays and check them in middleware.


Your Turn!

Have you ever struggled with setting up a login system that supports both admins and regular users? What was the trickiest part?

👇 Drop your story or tip in the comments — let’s learn together!

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts