Learn how to build a user permission management system with Replit. Follow this guide to manage roles, access levels, and secure your web app easily.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
A practical way to build a user permission management system on Replit is to create a Node.js + Express backend with authentication (using hashed passwords and JWT tokens) and a simple middleware that checks user roles (like “admin”, “editor”, “viewer”). You’ll store user data and their roles in a database (for Replit demos, SQLite works great and is included by default). Then, you add API routes that only certain roles can access. This approach works well on Replit and is easy to deploy, test, and extend — it’s also safe to use with Replit Secrets for environment variables.
Create the following files and folders in your Replit project root:
Also, go to the “Secrets” tab in Replit and create a new secret called JWT\_SECRET with any secure random string (this will keep JWT signing safe).
npm init -y
npm install express sqlite3 bcryptjs jsonwebtoken
// db.js
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(':memory:'); // for demo, use in-memory; or './users.db' for a file
// Create users table with name, password and role
db.serialize(() => {
db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT, role TEXT)');
});
module.exports = db;
// auth.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const db = require('./db');
const JWT_SECRET = process.env.JWT_SECRET;
// Register new user
function register(username, password, role, callback) {
bcrypt.hash(password, 10, (err, hash) => {
if (err) return callback(err);
db.run(
'INSERT INTO users (username, password, role) VALUES (?, ?, ?)',
[username, hash, role],
function (dbErr) {
if (dbErr) return callback(dbErr);
callback(null, { id: this.lastID, username, role });
}
);
});
}
// Login existing user
function login(username, password, callback) {
db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
if (err || !user) return callback(err || new Error('User not found'));
bcrypt.compare(password, user.password, (compErr, isMatch) => {
if (!isMatch) return callback(new Error('Invalid password'));
const token = jwt.sign(
{ id: user.id, username: user.username, role: user.role },
JWT_SECRET,
{ expiresIn: '1h' }
);
callback(null, token);
});
});
}
module.exports = { register, login };
// middleware/permission.js
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET;
function authorizeRole(allowedRoles) {
return (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(403).json({ message: 'No token provided' });
const token = authHeader.split(' ')[1];
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) return res.status(403).json({ message: 'Invalid token' });
if (!allowedRoles.includes(decoded.role)) {
return res.status(403).json({ message: 'Permission denied' });
}
req.user = decoded;
next();
});
};
}
module.exports = authorizeRole;
// index.js
const express = require('express');
const app = express();
const db = require('./db');
const { register, login } = require('./auth');
const authorizeRole = require('./middleware/permission');
app.use(express.json());
// Endpoint to register new user
app.post('/register', (req, res) => {
const { username, password, role } = req.body;
register(username, password, role, (err, user) => {
if (err) return res.status(400).json({ error: err.message });
res.json({ message: 'User registered', user });
});
});
// Endpoint to login
app.post('/login', (req, res) => {
const { username, password } = req.body;
login(username, password, (err, token) => {
if (err) return res.status(400).json({ error: err.message });
res.json({ token });
});
});
// Protected route for admin only
app.get('/admin', authorizeRole(['admin']), (req, res) => {
res.json({ message: `Welcome, ${req.user.username}! You have admin access.` });
});
// Protected route for editors and admins
app.get('/editor', authorizeRole(['admin', 'editor']), (req, res) => {
res.json({ message: `Hello ${req.user.username}, you can edit content.` });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
{"username":"alice","password":"1234","role":"admin"}
':memory:' to './users.db'.console.log() statements if you need to trace requests.
That’s a complete runnable user permission management setup that fits Replit’s environment perfectly — simple to maintain, test, and safe to deploy for demos or small projects.
import express from "express";
import Database from "@replit/database";
import jwt from "jsonwebtoken";
import bodyParser from "body-parser";
const app = express();
const db = new Database();
app.use(bodyParser.json());
const SECRET = process.env.JWT\_SECRET;
// Seed roles and permissions if they don't exist
const seedPermissions = async () => {
const existing = await db.get("roles");
if (!existing) {
await db.set("roles", {
admin: ["manage_users", "view_data", "edit\_data"],
editor: ["view_data", "edit_data"],
viewer: ["view\_data"]
});
}
};
seedPermissions();
app.post("/login", async (req, res) => {
const { username, role } = req.body;
const roles = (await db.get("roles")) || {};
if (!roles[role]) return res.status(400).send("Invalid role");
const token = jwt.sign({ username, role }, SECRET, { expiresIn: "2h" });
res.json({ token });
});
const checkPermission = (permission) => async (req, res, next) => {
const header = req.headers.authorization;
if (!header) return res.status(401).send("Token missing");
try {
const token = header.split(" ")[1];
const decoded = jwt.verify(token, SECRET);
const roles = (await db.get("roles")) || {};
const userPermissions = roles[decoded.role] || [];
if (!userPermissions.includes(permission)) return res.status(403).send("Forbidden");
req.user = decoded;
next();
} catch {
res.status(401).send("Invalid token");
}
};
app.get("/users", checkPermission("manage\_users"), async (req, res) => {
const users = (await db.list()).filter(k => k.startsWith("user:"));
res.json(users);
});
app.listen(3000, () => console.log("🚀 Permission API running on port 3000"));
import express from "express";
import Database from "@replit/database";
import fetch from "node-fetch";
const app = express();
const db = new Database();
// Example use case: Sync role changes from an external admin API (e.g., your org’s central RBAC service)
app.post("/sync-roles", async (req, res) => {
try {
const response = await fetch(process.env.EXTERNAL_ROLE_API\_URL, {
headers: { Authorization: `Bearer ${process.env.EXTERNAL_API_TOKEN}` },
});
if (!response.ok) return res.status(502).send("Failed to fetch roles from external service");
const externalRoles = await response.json();
const localRoles = (await db.get("roles")) || {};
// Merge new roles while preserving local permissions for comparison or rollback
const updatedRoles = { ...localRoles, ...externalRoles };
await db.set("roles", updatedRoles);
res.json({ message: "Roles synced successfully", updatedRoles });
} catch (err) {
console.error("Role sync failed:", err);
res.status(500).send("Unable to sync roles");
}
});
app.listen(4000, () => console.log("🔄 Role sync service running on port 4000"));
import express from "express";
import Database from "@replit/database";
import jwt from "jsonwebtoken";
const app = express();
const db = new Database();
const SECRET = process.env.JWT\_SECRET;
app.use(express.json());
// Middleware to ensure only admins can trigger audits
const requireAdmin = async (req, res, next) => {
const header = req.headers.authorization;
if (!header) return res.status(401).send("Missing token");
try {
const token = header.split(" ")[1];
const decoded = jwt.verify(token, SECRET);
if (decoded.role !== "admin") return res.status(403).send("Forbidden");
req.user = decoded;
next();
} catch {
res.status(401).send("Invalid token");
}
};
// Specific use case: detect any users assigned to non-existent roles
app.get("/audit/roles", requireAdmin, async (req, res) => {
try {
const keys = await db.list("user:");
const users = await Promise.all(keys.map(async k => await db.get(k)));
const roles = (await db.get("roles")) || {};
const invalidAssignments = users
.filter(u => !roles[u.role])
.map(u => ({ username: u.username, invalidRole: u.role }));
res.json({ invalidAssignments });
} catch (err) {
console.error("Audit failed:", err);
res.status(500).send("Audit error");
}
});
app.listen(5000, () => console.log("🧩 Role audit API running on port 5000"));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
When building a user permission management system in Replit, keep the logic simple and structured. Use role-based access control (RBAC) or permission-based checks stored in a database (like Replit’s built-in Database or an external one such as MongoDB Atlas). Store credentials and JWT secrets using the Replit Secrets tool, not in your code. Your main backend file (e.g., index.js for Node or main.py for Python) should handle routes, authentication, and role validations. Keep permission logic modular, like in a separate auth.js middleware or permissions.js helper.
Organize your files clearly inside your Replit project so permissions and authentication are readable and manageable.
// index.js
import express from "express";
import jwt from "jsonwebtoken";
import { checkRole } from "./auth.js";
const app = express();
app.use(express.json());
// Example users stored in memory for demo
const users = [
{ id: 1, username: "adminUser", role: "admin" },
{ id: 2, username: "editorUser", role: "editor" },
{ id: 3, username: "viewerUser", role: "viewer" }
];
// Generate token route
app.post("/login", (req, res) => {
const { username } = req.body;
const user = users.find(u => u.username === username);
if (!user) return res.status(403).send("Invalid username");
// Secret stored in Replit Secrets panel as JWT_SECRET
const token = jwt.sign({ id: user.id, role: user.role }, process.env.JWT_SECRET);
res.json({ token });
});
// A route restricted by role
app.get("/admin-data", checkRole("admin"), (req, res) => {
res.send("Secure admin data here");
});
// Start the server
app.listen(3000, () => console.log("Server running on port 3000"));
This file verifies tokens and checks if the user’s role fits the route requirement. Create auth.js in the root of your Replit project.
// auth.js
import jwt from "jsonwebtoken";
export function checkRole(requiredRole) {
return (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(401).send("No token provided");
const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (decoded.role !== requiredRole) {
return res.status(403).send("Insufficient permissions");
}
req.user = decoded;
next();
} catch (err) {
res.status(401).send("Invalid token");
}
};
}
You can extend roles and permissions easily from here. For complex projects, store these in a database table.
// permissions.js
export const rolesPermissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"]
};
// Helper to verify a user's action
export function can(role, action) {
return rolesPermissions[role]?.includes(action);
}
Never hardcode secrets directly in code. In Replit:
process.env.JWT\_SECRET.
If you need persistent user data, connect Replit DB or MongoDB:
// Example using Replit DB for simple user storage
import Database from "@replit/database";
const db = new Database();
await db.set("users", users);
const storedUsers = await db.get("users");
This setup is minimal but production-valid in Replit’s sandbox. It keeps authentication and permission checks modular, secrets secure, and logic easy to extend — matching how a senior developer would structure Node-based projects inside Replit’s environment.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.