Node.js Authorization — Complete Guide to Role-Based Access Control
In this tutorial, you will learn about Node.js Authorization. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js authorization controls what authenticated users can do, implementing role-based access control (RBAC), permissions, and resource-level access rules.
What You'll Learn
By the end of this tutorial, you'll implement RBAC with roles and permissions, create authorization middleware, protect API routes, and handle resource-level ownership checks.
Why Authorization Matters
Authentication tells you who the user is. Authorization tells you what they can do. Without proper authorization, users can access other users' data or perform admin actions.
Real-World Use
A project management app has three roles: Admin (all access), Manager (create/edit projects), Member (view and comment). Authorization middleware ensures each user can only perform allowed actions.
Authorization Learning Path
flowchart LR
A[Authentication] --> B[Authorization]
B --> C[File Upload]
C --> D[Caching]
D --> E[Docker]
A --> F{You Are Here}
style F fill:#f90,color:#fff
Role-Based Access Control
const ROLES = { ADMIN: "admin", MANAGER: "manager", USER: "user" };
const PERMISSIONS = {
[ROLES.ADMIN]: ["read:users", "create:users", "delete:users", "read:reports"],
[ROLES.MANAGER]: ["read:users", "create:users", "read:reports"],
[ROLES.USER]: ["read:own"]
};
function hasPermission(user, permission) {
const permissions = PERMISSIONS[user.role];
return permissions && permissions.includes(permission);
}
Authorization Middleware
function authorize(...allowedRoles) {
return (req, res, next) => {
if (!req.user) return res.status(401).json({ error: "Not authenticated" });
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
}
// Usage
app.get("/api/admin/users", authenticate, authorize("admin"), async (req, res) => {
const users = await db.users.findAll();
res.json(users);
});
Resource Ownership
async function checkOwnership(model, paramName = "id") {
return async (req, res, next) => {
const resource = await db[model].findById(req.params[paramName]);
if (!resource) return res.status(404).json({ error: "Not found" });
if (resource.userId !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Access denied" });
}
req.resource = resource;
next();
};
}
app.get("/api/posts/:id", authenticate, checkOwnership("posts"), (req, res) => {
res.json(req.resource);
});
Permission-Based Middleware
function requirePermission(permission) {
return (req, res, next) => {
if (!req.user) return res.status(401).json({ error: "Not authenticated" });
if (!hasPermission(req.user, permission)) {
return res.status(403).json({ error: "Permission denied" });
}
next();
};
}
app.delete("/api/users/:id", authenticate, requirePermission("delete:users"), handler);
ACL Implementation
const acl = new Map();
function grant(userId, resource, action) {
const key = `${userId}:${resource}`;
if (!acl.has(key)) acl.set(key, []);
acl.get(key).push(action);
}
function checkAccess(userId, resource, action) {
const key = `${userId}:${resource}`;
const actions = acl.get(key);
return actions && actions.includes(action);
}
Common Mistakes
1. Only Checking Authentication, Not Authorization
Authenticated doesn't mean authorized. Always check permissions after authentication.
2. Hardcoding Role Checks with Magic Strings
Use constants for role names. Hardcoded strings cause bugs when role names change.
3. Forgetting Resource Ownership
A user with role "user" should only access their own resources. Check ownership in addition to roles.
4. Returning 401 vs 403 Incorrectly
401 means unauthenticated (not logged in). 403 means authenticated but not authorized (wrong role). Use the correct status code.
5. Not Testing Authorization Edge Cases
Test that unauthorized users get 403 for every protected route. Test that authorized users can access their routes.
Practice Questions
1. What is the difference between authentication and authorization?
Authentication verifies identity. Authorization determines what actions the authenticated user can perform.
2. What is RBAC?
Role-Based Access Control assigns permissions to roles and roles to users. A user's role determines what they can do.
3. How do you implement resource-level authorization?
Check that the resource's owner ID matches the authenticated user's ID (or the user has admin role).
4. What HTTP status code indicates insufficient permissions?
403 Forbidden. 401 is for missing or invalid authentication.
5. Challenge: Implement a permission system where admins can delete any post, but users can only delete their own posts.
app.delete("/api/posts/:id", authenticate, async (req, res) => {
const post = await db.posts.findById(req.params.id);
if (!post) return res.status(404).json({ error: "Not found" });
if (post.userId !== req.user.id && req.user.role !== "admin") {
return res.status(403).json({ error: "Not authorized" });
}
await db.posts.delete(req.params.id);
res.status(204).send();
});
FAQ
Mini Project: RBAC System
Build a complete RBAC system with roles, permissions, and middleware.
const ROLES = { ADMIN: "admin", EDITOR: "editor", VIEWER: "viewer" };
const PERMISSIONS = {
[ROLES.ADMIN]: ["create", "read", "update", "delete"],
[ROLES.EDITOR]: ["create", "read", "update"],
[ROLES.VIEWER]: ["read"]
};
function authorize(action) {
return (req, res, next) => {
const userPerms = PERMISSIONS[req.user?.role];
if (!userPerms?.includes(action)) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
}
app.post("/api/articles", authenticate, authorize("create"), handler);
app.get("/api/articles", authenticate, authorize("read"), handler);
app.put("/api/articles/:id", authenticate, authorize("update"), handler);
app.delete("/api/articles/:id", authenticate, authorize("delete"), handler);
What's Next
Node.js File Upload Node.js Caching Redis Node.js Docker
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro