Introduction
System Architecture
CREATE TABLE roles ( id BIGINT PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE );
CREATE TABLE permissions ( id BIGINT PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE );
CREATE TABLE user_role ( user_id BIGINT REFERENCES users(id), role_id BIGINT REFERENCES roles(id), PRIMARY KEY (user_id, role_id) );
CREATE TABLE role_permission ( role_id BIGINT REFERENCES roles(id), permission_id BIGINT REFERENCES permissions(id), PRIMARY KEY (role_id, permission_id) ); </pre>
<p>With this schema you can answer the classic RBAC question: <em>"Does user X have permission Y?"</em> by joining <code>user_role</code> and <code>role_permission</code> tables.</p>Code Implementation
const User = sequelize.define('User', { username: { type: DataTypes.STRING, unique: true, allowNull: false }, passwordHash: { type: DataTypes.STRING, allowNull: false } });
const Role = sequelize.define('Role', { name: { type: DataTypes.STRING, unique: true } }); const Permission = sequelize.define('Permission', { name: { type: DataTypes.STRING, unique: true } });
// Junction tables User.belongsToMany(Role, { through: 'user_role' }); Role.belongsToMany(User, { through: 'user_role' }); Role.belongsToMany(Permission, { through: 'role_permission' }); Permission.belongsToMany(Role, { through: 'role_permission' });
module.exports = { sequelize, User, Role, Permission }; </pre>
<h3>3️⃣ Authentication Service (auth.js)</h3> <pre> const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const { User } = require('./models');const JWT_SECRET = process.env.JWT_SECRET || 'super‑secret-key';
// Register a new user (demo only) async function register(username, password) { const passwordHash = await bcrypt.hash(password, 10); return User.create({ username, passwordHash }); }
// Login & issue JWT async function login(username, password) { const user = await User.findOne({ where: { username } }); if (!user) throw new Error('Invalid credentials'); const match = await bcrypt.compare(password, user.passwordHash); if (!match) throw new Error('Invalid credentials'); const payload = { sub: user.id, username: user.username }; return jwt.sign(payload, JWT_SECRET, { expiresIn: '1h' }); }
module.exports = { register, login, JWT_SECRET }; </pre>
<h3>4️⃣ RBAC Middleware (rbac.js)</h3> <pre> const jwt = require('jsonwebtoken'); const { User, Role, Permission } = require('./models'); const { JWT_SECRET } = require('./auth');// Helper: fetch all permissions for a user async function loadUserPermissions(userId) { const user = await User.findByPk(userId, { include: [{ model: Role, include: [Permission] }] }); const perms = new Set(); user.Roles.forEach(role => { role.Permissions.forEach(p => perms.add(p.name)); }); return perms; }
// Middleware factory - checks a required permission string function authorize(requiredPermission) { return async (req, res, next) => { try { const authHeader = req.headers['authorization']; if (!authHeader) return res.status(401).json({ error: 'Missing token' }); const token = authHeader.split(' ')[1]; const payload = jwt.verify(token, JWT_SECRET); req.user = { id: payload.sub, username: payload.username };
const permissions = await loadUserPermissions(req.user.id);
if (!permissions.has(requiredPermission)) {
return res.status(403).json({ error: 'Forbidden - insufficient rights' });
}
next();
} catch (err) {
console.error(err);
res.status(401).json({ error: 'Invalid token' });
}
}; }
module.exports = { authorize }; </pre>
<h3>5️⃣ Protecting Routes (server.js)</h3> <pre> const express = require('express'); const { sequelize } = require('./models'); const { register, login } = require('./auth'); const { authorize } = require('./rbac');const app = express(); app.use(express.json());
// Public endpoints app.post('/register', async (req, res) => { const { username, password } = req.body; const user = await register(username, password); res.json({ id: user.id, username: user.username }); });
app.post('/login', async (req, res) => { const { username, password } = req.body; const token = await login(username, password); res.json({ token }); });
// Protected - only users with "read:reports" can access app.get('/reports', authorize('read:reports'), (req, res) => { res.json({ data: 'Sensitive report data for ' + req.user.username }); });
// Protected - requires admin role (admin has "manage:users") app.delete('/users/:id', authorize('manage:users'), async (req, res) => { // Deletion logic … res.json({ status: 'User removed' }); });
// Initialise DB & start server (async () => { await sequelize.sync({ force: false }); // set force:true for first‑time demo app.listen(3000, () => console.log('RBAC demo listening on port 3000')); })(); </pre>
<p>Notice that the <code>authorize</code> middleware is reusable for any permission string, keeping route handlers clean and focused on business concerns.</p>