User Authentication & Authorization System in Node.js

surya nagsurya nag
5 min read

In the world of modern web development, authentication and authorization are two vital pillars for building secure applications. Whether you're crafting a personal blog or deploying a full-scale SaaS platform, securing your app's user data is non-negotiable.

This mini project will guide you in building a basic auth system using Node.js, Express, MongoDB, and JWT, where users can:

βœ… Register
βœ… Login
βœ… Access Protected Routes


🧠 Understanding the Core Concepts

βœ… Authentication – β€œWho are you?”

Authentication is the process of confirming a user's identity β€” like logging in using email and password.

Analogy: Think of it as showing your ID at a building’s entrance.

πŸ“Œ Examples:

  • Logging in with email/password

  • Signing in via Google or GitHub


βœ… Authorization – β€œWhat are you allowed to do?”

Authorization defines what actions a verified user is permitted to take.

Analogy: Once you're inside, your ID badge determines which rooms you can enter.

πŸ“Œ Examples:

  • Admins can delete users

  • Users can only edit their own profiles


βš™οΈ Step 1: Project Setup

πŸ—‚ Folder Structure

pgsqlCopyEditmini-auth-system/
β”œβ”€β”€ controllers/
β”‚   └── authController.js
β”œβ”€β”€ models/
β”‚   └── user.js
β”œβ”€β”€ routes/
β”‚   └── authRoutes.js
β”œβ”€β”€ middleware/
β”‚   └── authMiddleware.js
β”œβ”€β”€ .env
β”œβ”€β”€ server.js
β”œβ”€β”€ app.js
β”œβ”€β”€ package.json

πŸ“¦ Required NPM Packages

Install these dependencies:

bashCopyEditnpm install express mongoose bcryptjs jsonwebtoken dotenv cors cookie-parser

πŸ›  Add .env file

iniCopyEditPORT=5000
MONGO_URI=mongodb://localhost:27017/authdemo
JWT_SECRET=supersecretkey

πŸš€ Step 2: Initialize Server

server.js

jsCopyEditimport app from "./app.js";
import mongoose from "mongoose";
import dotenv from "dotenv";
dotenv.config();

mongoose.connect(process.env.MONGO_URI)
  .then(() => {
    console.log("MongoDB connected");
    app.listen(process.env.PORT, () =>
      console.log(`Server running at port ${process.env.PORT}`)
    );
  })
  .catch((err) => console.error("MongoDB connection error:", err));

🌐 Step 3: Set Up Express App

app.js

jsCopyEditimport express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import authRoutes from "./routes/authRoutes.js";

const app = express();

app.use(cors({
  origin: "http://localhost:3000", // Frontend domain
  credentials: true
}));
app.use(express.json());
app.use(cookieParser());

app.use("/api/auth", authRoutes);

export default app;

πŸ‘€ Step 4: Create the User Model

models/User.js

jsCopyEditimport mongoose from "mongoose";
import bcrypt from "bcryptjs";

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

// Hash password before saving
userSchema.pre("save", async function (next) {
  if (!this.isModified("password")) return next();
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

// Method to compare password
userSchema.methods.comparePassword = function (passwordInput) {
  return bcrypt.compare(passwordInput, this.password);
};

export default mongoose.model("User", userSchema);

πŸ” Step 5: Auth Controller – Register & Login Logic

controllers/authController.js

jsCopyEditimport User from "../models/User.js";
import jwt from "jsonwebtoken";

const generateToken = (userId) => {
  return jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: "1d" });
};

const cookieOptions = {
  httpOnly: true,
  secure: process.env.NODE_ENV === "production",
  sameSite: "strict",
  maxAge: 24 * 60 * 60 * 1000,
};

// Register
export const registerUser = async (req, res) => {
  try {
    const { name, email, password } = req.body;
    const userExists = await User.findOne({ email });
    if (userExists)
      return res.status(400).json({ message: "User already registered" });

    const newUser = await User.create({ name, email, password });
    const token = generateToken(newUser._id);
    res.cookie("token", token, cookieOptions);

    res.status(201).json({
      message: "User registered",
      user: { id: newUser._id, name: newUser.name, email: newUser.email }
    });
  } catch (error) {
    res.status(500).json({ message: "Registration failed", error: error.message });
  }
};

// Login
export const loginUser = async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email });
    if (!user) return res.status(401).json({ message: "Invalid credentials" });

    const isMatch = await user.comparePassword(password);
    if (!isMatch) return res.status(401).json({ message: "Invalid credentials" });

    const token = generateToken(user._id);
    res.cookie("token", token, cookieOptions);

    res.status(200).json({
      message: "Login successful",
      user: { id: user._id, name: user.name, email: user.email }
    });
  } catch (error) {
    res.status(500).json({ message: "Login error", error: error.message });
  }
};

πŸ”’ Step 6: Middleware for Protected Routes

middleware/authMiddleware.js

jsCopyEditimport jwt from "jsonwebtoken";
import User from "../models/User.js";

const protect = async (req, res, next) => {
  const token = req.cookies.token;
  if (!token) return res.status(401).json({ message: "Unauthorized" });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select("-password");
    next();
  } catch (err) {
    res.status(401).json({ message: "Token is invalid" });
  }
};

export default protect;

πŸ›£οΈ Step 7: Define Auth Routes

routes/authRoutes.js

jsCopyEditimport express from "express";
import { registerUser, loginUser } from "../controllers/authController.js";
import protect from "../middleware/authMiddleware.js";

const router = express.Router();

router.post("/register", registerUser);
router.post("/login", loginUser);

// Sample protected route
router.get("/profile", protect, (req, res) => {
  res.status(200).json({
    message: "Protected content access granted",
    user: req.user
  });
});

export default router;

🎯 Final Notes

You’ve now successfully created a JWT-based auth system with secure cookie storage and protected routing!

πŸ”§ Key Features:

  • Password hashing using bcryptjs

  • JWT token creation using jsonwebtoken

  • Secure cookie storage

  • Middleware-based route protection


🧠 Challenge: Try This Next!

πŸ“ Implement Logout Functionality
➑ Clear the token cookie and send back a success message.

This will complete the full login-logout lifecycle for your mini-auth system. Try it out on your own β€” it’s a great way to build confidence.

🧩 Expand the Project

Here are some ideas to enhance this project further:

  • πŸ“¨ Add Email Verification

  • πŸ§‘β€βš–οΈ Implement Role-based Access (admin/user)

  • πŸ”— Connect with a React or Next.js frontend

  • πŸ“† Token Refresh with long-lived sessions

0
Subscribe to my newsletter

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

Written by

surya nag
surya nag