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)