DEV Community

Cover image for πŸ” Implementing Secure JWT Authentication in Node.js with MySQL & Role-Based Access Control (RBAC)
Brian Oiko
Brian Oiko

Posted on

πŸ” Implementing Secure JWT Authentication in Node.js with MySQL & Role-Based Access Control (RBAC)

🧰 Tech Stack

Node.js & Express – Backend framework

Sequelize – ORM for MySQL

JWT – Stateless authentication

bcryptjs – Password hashing

dotenv – Environment variables

CORS – Cross-origin resource sharing
Enter fullscreen mode Exit fullscreen mode

πŸ“ Project Structure

src/
β”œβ”€β”€ config/ # DB & environment config
β”œβ”€β”€ controllers/ # Auth logic
β”œβ”€β”€ middlewares/ # Token & role guards
β”œβ”€β”€ models/ # Sequelize models
β”œβ”€β”€ routes/ # API routes
└── server.js # App entry point

βš™οΈ Step 1: Initialize Project

Install Dependencies:

npm install express sequelize mysql2 cors jsonwebtoken bcryptjs dotenv
Enter fullscreen mode Exit fullscreen mode
  1. Database Configuration with Sequelize

Set up PostgreSQL credentials and define a Sequelize instance. The Tutorial model contains fields for title, description and published status. You can run PostgreSQL locally, including via Docker for convenience in development.
Configure MySQL – config/database.js:

module.exports = {
HOST: "localhost",
USER: "root",
PASSWORD: "yourpassword",
DB: "jwt_auth_db",
dialect: "mysql",
};
**
🧬 Step 2: Define Sequelize Models**

models/user.model.js:

const User = sequelize.define("user", {
username: { type: Sequelize.STRING, unique: true },
email: { type: Sequelize.STRING, unique: true },
password: Sequelize.STRING,
});

models/role.model.js:

const Role = sequelize.define("role", {
name: { type: Sequelize.STRING, unique: true },
});

Define Relationship:

User.belongsToMany(Role, { through: "user_roles" });
Role.belongsToMany(User, { through: "user_roles" });

πŸ” Step 3: JWT Authentication Logic
βœ… Signup Endpoint

  1. Implementing RESTful API Routes

Create controllers for each CRUD operation: creating, finding (all or by ID), updating and deleting tutorials. Map these controllers to Express routes like /api/tutorials and /api/tutorials/:id. Each endpoint enables a RESTful API ideal for single-page applications.

const bcrypt = require("bcryptjs");

exports.signup = async (req, res) => {
try {
const { username, email, password } = req.body;
const hashedPassword = bcrypt.hashSync(password, 8);

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

const defaultRole = await Role.findOne({ where: { name: "user" } });
await user.addRole(defaultRole);

res.send({ message: "User registered successfully!" });
Enter fullscreen mode Exit fullscreen mode

} catch (err) {
res.status(500).send({ message: err.message });
}
};

πŸ”“ Signin Endpoint

const jwt = require("jsonwebtoken");

exports.signin = async (req, res) => {
try {
const user = await User.findOne({ where: { username: req.body.username } });
if (!user) return res.status(404).send({ message: "User not found." });

const valid = bcrypt.compareSync(req.body.password, user.password);
if (!valid) return res.status(401).send({ message: "Invalid password." });

const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: "1h" });

res.status(200).send({
  id: user.id,
  username: user.username,
  email: user.email,
  accessToken: token,
});
Enter fullscreen mode Exit fullscreen mode

} catch (err) {
res.status(500).send({ message: err.message });
}
};

πŸ›‘οΈ Step 4: Middleware for Security
πŸ”‘ Verify JWT Token

const jwt = require("jsonwebtoken");

exports.verifyToken = (req, res, next) => {
const token = req.headers["x-access-token"];
if (!token) return res.status(403).send({ message: "No token provided!" });

jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) return res.status(401).send({ message: "Unauthorized!" });
req.userId = decoded.id;
next();
});
};

🧠 Role-Based Access

exports.isAdmin = async (req, res, next) => {
const user = await User.findByPk(req.userId, { include: ["roles"] });
const isAdmin = user.roles.some(role => role.name === "admin");

if (isAdmin) next();
else res.status(403).send({ message: "Requires Admin Role!" });
};

πŸš€ Step 5: Protect Routes

routes/user.routes.js:

const { verifyToken, isAdmin } = require("../middlewares/authJwt");

router.get("/admin-only", [verifyToken, isAdmin], (req, res) => {
res.send("Admin Content Only");
});
**
πŸ§ͺ Step 6: Test API with cURL/Postman**
Register User

curl -X POST http://localhost:3000/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"username":"test","email":"test@example.com","password":"123456"}'

Login to Get Token

curl -X POST http://localhost:3000/api/auth/signin \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"123456"}'

Access Protected Route

curl -X GET http://localhost:3000/api/test/admin \
-H "x-access-token: YOUR_JWT_TOKEN"

  1. Running Your Full-Stack App

Start your Express server on port 3000, ensuring the database syncs via Sequelize on launch. Launch your React application with npm run dev (typically on port 3000). Ensure both backend and frontend are running locally to allow for seamless, real-time development and testing.

πŸ”’ Best Security Practices

βœ… Use .env for secrets and DB credentials
βœ… Limit token lifetime (e.g., 1 hour)
βœ… Use helmet to secure HTTP headers
βœ… Enable rate limiting
βœ… Always hash passwords securely with bcryptjs
βœ… Conclusion

By following this guide, you now have:

πŸ” Secure JWT authentication

🧬 MySQL user-role mapping via Sequelize

🧱 Role-Based Access Control (RBAC)

⚑ Production-ready and scalable backend foundation
Enter fullscreen mode Exit fullscreen mode

**
Key Takeaways: Full-Stack JavaScript Best Practices**

The combination of React, Node.js, Express and PostgreSQL is a proven tech stack for dynamic, scalable apps.
Sequelize ORM streamlines database interaction and model management in a Node.js environment.
Modular architecture keeps services, components and routes maintainable and extensible.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)