User Authentication & Authorization System in Node.js


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
Subscribe to my newsletter
Read articles from surya nag directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
