You've authenticated your users, you know who they are. Now you need to decide what they're allowed to do. Alice is an admin who can delete any post. Bob is a regular user who can only delete his own posts. Charlie is a moderator who can delete others' posts but can't manage users.
This is authorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages., and Role-Based Access ControlWhat is rbac?Role-Based Access Control - assigning permissions to roles (like admin or editor), then giving users roles instead of individual permissions. (RBAC) is the most common way to implement it.
Think of RBAC like employee badges at a company. Instead of programming each door with individual employee names, you assign roles: "Employee", "Manager", "IT Admin". Each role gets a set of permissions, and employees get the appropriate badge. When someone gets promoted, you just swap their badge, you don't reprogram every door.
The RBACWhat is rbac?Role-Based Access Control - assigning permissions to roles (like admin or editor), then giving users roles instead of individual permissions. model
RBAC has three main components:
- Users: People using your system
- Roles: Groups that define what users can do (admin, user, moderator)
- Permissions: Specific actions allowed (create:post, delete:user, read:report)
The relationship looks like this:
User Alice ──┐
├──→ Admin Role ──┐
User Bob ───┘ ├──→ [create, read, update, delete]
│
User Carol ──→ Editor Role ────┘Simple RBACWhat is rbac?Role-Based Access Control - assigning permissions to roles (like admin or editor), then giving users roles instead of individual permissions. implementation
For most applications, you don't need a complex database schemaWhat is schema?A formal definition of the structure your data must follow - which fields exist, what types they have, and which are required.. A simple string column for the role is enough:
// Database schema (simplified)
// users table: id, email, password_hash, role
// role column values: 'user', 'admin', 'moderator'
// Middleware to check roles
function requireRole(...allowedRoles) {
return (req, res, next) => {
// First, check if user is authenticated
if (!req.user) {
return res.status(401).json({
error: 'Authentication required'
});
}
// Then, check if user's role is allowed
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({
error: 'Insufficient permissions'
});
}
// User has permission, continue
next();
};
}
// Usage in routes
// Only admins can access the dashboard
app.get('/admin/dashboard',
requireAuth, // First verify authentication
requireRole('admin'), // Then check authorization
adminController
);
// Multiple roles can create posts
app.post('/posts',
requireAuth,
requireRole('user', 'admin', 'moderator'),
createPost
);
// Only admins and moderators can delete any post
app.delete('/posts/:id',
requireAuth,
requireRole('admin', 'moderator'),
deletePost
);Notice the status codes:
- 401 Unauthorized: Not authenticated (don't know who you are)
- 403 Forbidden: Authenticated but not allowed (know who you are, but you can't do this)
Granular permissions with RBACWhat is rbac?Role-Based Access Control - assigning permissions to roles (like admin or editor), then giving users roles instead of individual permissions.
As your app grows, simple roles might not be enough. You might need specific permissions within roles. Here's a more detailed approach:
// Define all possible permissions
const PERMISSIONS = {
USERS: {
READ: 'users:read',
CREATE: 'users:create',
UPDATE: 'users:update',
DELETE: 'users:delete',
UPDATE_OWN: 'users:update:own' // Can update own profile
},
POSTS: {
READ: 'posts:read',
CREATE: 'posts:create',
UPDATE: 'posts:update',
DELETE: 'posts:delete',
UPDATE_OWN: 'posts:update:own',
DELETE_OWN: 'posts:delete:own'
}
};
// Define what each role can do
const ROLES = {
admin: [
PERMISSIONS.USERS.READ,
PERMISSIONS.USERS.CREATE,
PERMISSIONS.USERS.UPDATE,
PERMISSIONS.USERS.DELETE,
PERMISSIONS.POSTS.READ,
PERMISSIONS.POSTS.CREATE,
PERMISSIONS.POSTS.UPDATE,
PERMISSIONS.POSTS.DELETE
],
user: [
PERMISSIONS.USERS.READ,
PERMISSIONS.USERS.UPDATE_OWN,
PERMISSIONS.POSTS.READ,
PERMISSIONS.POSTS.CREATE,
PERMISSIONS.POSTS.UPDATE_OWN,
PERMISSIONS.POSTS.DELETE_OWN
],
moderator: [
PERMISSIONS.USERS.READ,
PERMISSIONS.POSTS.READ,
PERMISSIONS.POSTS.CREATE,
PERMISSIONS.POSTS.UPDATE,
PERMISSIONS.POSTS.DELETE
]
};
// Middleware to check specific permissions
function requirePermission(permission) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
const userPermissions = ROLES[req.user.role] || [];
if (!userPermissions.includes(permission)) {
return res.status(403).json({ error: 'Permission denied' });
}
next();
};
}
// Usage
app.delete('/posts/:id',
requireAuth,
requirePermission(PERMISSIONS.POSTS.DELETE),
deletePost
);This approach gives you more flexibility. If you later want to add a "Content Editor" role that can update posts but not delete them, you just define a new role with specific permissions, no code changes needed.
Resource ownership
Often, users should be able to edit their own resources but not others. This combines authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. (who are you?) with authorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages. (is this yours?).
// Middleware that checks ownership OR permission
function requireOwnershipOrPermission(getResource, permission) {
return async (req, res, next) => {
// Get the resource from database
const resource = await getResource(req.params.id);
if (!resource) {
return res.status(404).json({ error: 'Resource not found' });
}
// Check if user is the owner
const isOwner = resource.userId === req.user.id;
// Check if user has the required permission
const userPermissions = ROLES[req.user.role] || [];
const hasPermission = userPermissions.includes(permission);
// Allow if owner OR has permission
if (!isOwner && !hasPermission) {
return res.status(403).json({
error: 'Not authorized to modify this resource'
});
}
// Attach resource to request for route handler
req.resource = resource;
next();
};
}
// Usage: Users can edit their own posts, moderators can edit any post
app.put('/posts/:id',
requireAuth,
requireOwnershipOrPermission(
id => db.posts.findById(id),
PERMISSIONS.POSTS.UPDATE
),
updatePost
);Role hierarchies
Sometimes roles have natural hierarchies. An admin can do everything a moderator can do, and more.
// Define role levels (higher number = more power)
const ROLE_HIERARCHY = {
admin: 3,
moderator: 2,
user: 1,
guest: 0
};
function requireMinRole(minRole) {
return (req, res, next) => {
const userLevel = ROLE_HIERARCHY[req.user.role] || 0;
const requiredLevel = ROLE_HIERARCHY[minRole];
if (userLevel < requiredLevel) {
return res.status(403).json({
error: 'Insufficient role level'
});
}
next();
};
}
// Usage: Accessible to moderators and above
app.get('/moderator/dashboard',
requireAuth,
requireMinRole('moderator'),
moderatorController
);Quick reference: RBACWhat is rbac?Role-Based Access Control - assigning permissions to roles (like admin or editor), then giving users roles instead of individual permissions. patterns
| Pattern | Use case | Example |
|---|---|---|
| Simple roles | Small apps with clear role separation | Admin vs User |
| Granular permissions | Complex apps with fine-grained control | posts:create, posts:delete |
| Ownership | Users manage their own content | Edit own profile |
| Hierarchy | Roles with natural levels | Admin > Moderator > User |
RBAC isn't just about security, it's about maintainability. When Alice gets promoted to admin, you change one field in the database. When you add a new feature, you define which roles can use it. Clean, scalable, and predictable.