DEV Community

Cover image for Building a Secure Authentication System in MERN Stack: My Experience with JWT, Refresh Tokens, RBAC, and CORS
Sandeep Dara
Sandeep Dara

Posted on

Building a Secure Authentication System in MERN Stack: My Experience with JWT, Refresh Tokens, RBAC, and CORS

Introduction

Hey everyone! If you've ever worked on a full-stack project, you know that authentication and authorization can be a real headache. When I first started working with the MERN stack (MongoDB, Express.js, React.js, Node.js), I struggled with setting up a solid authentication system. Debugging token issues, handling user sessions, and dealing with CORS errors were some of the most frustrating parts. But after digging deep and trying out different approaches, I finally figured out how to do it the right way.

In this blog, I'll share my experience building a secure authentication system using JWT (JSON Web Tokens), Refresh Tokens, and Role-Based Access Control (RBAC). I'll also cover CORS (Cross-Origin Resource Sharing) and how to configure it properly. Let's get started! πŸš€


1. Authentication vs. Authorization (The Confusion I Had)

When I started, I used to mix up authentication and authorization. Here’s the difference:

  • Authentication: Verifying who the user is (e.g., logging in with an email and password).
  • Authorization: Deciding what the authenticated user can do (e.g., admin vs. regular user access).

Once I got this straight, things became a lot easier.


2. Implementing JWT-Based Authentication

My Experience with JWT

JWT (JSON Web Token) is a widely used way to handle authentication in web apps. At first, I found it confusing, but once I understood its structure, it made perfect sense. A JWT consists of:

  • Header (Algorithm & Token Type)
  • Payload (User Information & Claims)
  • Signature (Verifies authenticity)

How I Implemented JWT Authentication in MERN

Backend (Node.js + Express)

  1. Installed the required dependencies:
   npm install jsonwebtoken bcryptjs dotenv
Enter fullscreen mode Exit fullscreen mode
  1. Wrote a function to generate JWT tokens:
   const jwt = require("jsonwebtoken");
   const generateToken = (userId) => {
       return jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: "1h" });
   };
Enter fullscreen mode Exit fullscreen mode
  1. Created middleware to protect routes:
   const authMiddleware = (req, res, next) => {
       const token = req.header("Authorization").replace("Bearer ", "");
       if (!token) return res.status(401).json({ message: "No token, authorization denied" });

       try {
           const decoded = jwt.verify(token, process.env.JWT_SECRET);
           req.user = decoded;
           next();
       } catch (error) {
           res.status(401).json({ message: "Invalid token" });
       }
   };
Enter fullscreen mode Exit fullscreen mode

Frontend (React.js)

  • I stored the JWT token securely in HTTP-only cookies instead of localStorage for better security.
  • Every protected API request included the token in the Authorization header.

3. Refresh Tokens (Fixing Expired Sessions)

I noticed that JWTs expire quickly (for security reasons), which frustrated users since they had to log in frequently. The solution? Refresh Tokens.

How I Implemented Refresh Tokens

  1. Generated a refresh token along with the access token:
   const refreshToken = jwt.sign({ id: user._id }, process.env.REFRESH_SECRET, { expiresIn: "7d" });
Enter fullscreen mode Exit fullscreen mode
  1. Stored the refresh token securely (e.g., in an HTTP-only cookie).
  2. Created an endpoint to refresh the JWT token:
   app.post("/refresh-token", (req, res) => {
       const { refreshToken } = req.body;
       jwt.verify(refreshToken, process.env.REFRESH_SECRET, (err, user) => {
           if (err) return res.status(403).json({ message: "Invalid refresh token" });
           const newToken = generateToken(user.id);
           res.json({ token: newToken });
       });
   });
Enter fullscreen mode Exit fullscreen mode

4. Role-Based Access Control (RBAC) - Making My App Secure

Why I Needed RBAC

I wanted admin users to have extra privileges while regular users had limited access. That’s where RBAC (Role-Based Access Control) helped me.

How I Implemented RBAC

  1. Added a role field to the User model:
   const UserSchema = new mongoose.Schema({
       username: String,
       email: String,
       password: String,
       role: { type: String, enum: ["admin", "user"], default: "user" }
   });
Enter fullscreen mode Exit fullscreen mode
  1. Created middleware to protect admin routes:
   const roleMiddleware = (roles) => (req, res, next) => {
       if (!roles.includes(req.user.role)) {
           return res.status(403).json({ message: "Access denied" });
       }
       next();
   };
Enter fullscreen mode Exit fullscreen mode
  1. Applied it to admin routes:
   app.get("/admin-dashboard", authMiddleware, roleMiddleware(["admin"]), (req, res) => {
       res.json({ message: "Welcome to the admin dashboard" });
   });
Enter fullscreen mode Exit fullscreen mode

5. CORS (Fixing Annoying Frontend Errors)

My Issue with CORS

At one point, my frontend (React) was unable to call my backend (Node.js) due to CORS errors. I fixed it by properly configuring CORS in my Express server.

Fixing CORS in Express.js

  1. Installed the CORS package:
   npm install cors
Enter fullscreen mode Exit fullscreen mode
  1. Configured CORS in Express.js:
   const cors = require("cors");
   app.use(cors({
       origin: "http://localhost:3000", // React frontend
       credentials: true,
   }));
Enter fullscreen mode Exit fullscreen mode
  1. Sent requests from React using:
   axios.get("http://localhost:5000/api", { withCredentials: true });
Enter fullscreen mode Exit fullscreen mode

Conclusion

This journey of implementing JWT, refresh tokens, RBAC, and CORS in my MERN app taught me a lot. Debugging authentication issues was frustrating at first, but solving them helped me gain a deep understanding of how security works in full-stack applications. If you're working on authentication, I hope this guide helps you avoid the struggles I faced! πŸš€

Let me know if you've faced similar issues or have any better approaches! Drop your thoughts in the comments. 😊

Top comments (0)