blog-post

Mastering Permission Systems for Scalable Web Applications

author image

Managing permissions is one of the most critical aspects of building scalable, maintainable web applications. Yet, many developers start with overly simplistic methods that quickly become unwieldy. In this post, we’ll explore three robust permission systems — Role-Based Access Control (RBAC), Attribute-Based Access Control (ABAC), and hybrid multi-tenancy setups — providing clear, actionable advice you can apply today.


Why Role-Based Systems Fall Short

Early in development, you might use basic conditional checks like:

if (user.role === 'admin') {
    // Allow deletion
}

This seems simple, but problems arise as roles expand. Adding a “moderator” or allowing users to delete their own content leads to sprawling if statements:

if (user.role === 'admin' || user.role === 'moderator' || user.id === comment.authorId) {
    // Allow deletion
}

These checks, scattered across your codebase, make maintenance a nightmare. Updating one permission requires modifying multiple files, increasing the risk of errors.

The solution? Centralized and scalable permission systems.


Implement Role-Based Access Control (RBAC)

RBAC simplifies permission logic by associating roles with predefined actions. Instead of coding permissions everywhere, you maintain a single configuration file or database table.

Example Configuration:

const roles = {
    admin: ['delete:comments', 'edit:comments'],
    moderator: ['delete:comments'],
    user: ['delete:own_comments'],
};

function hasPermission(user, action) {
    return roles[user.role]?.includes(action);
}

Why RBAC Works:

  • Centralized Management: One source of truth for all permissions.
  • Scalability: Adding new roles or actions doesn’t require rewriting logic.

Limitations:
RBAC struggles with contextual permissions, like allowing a user to delete comments only when they are the author.


Embrace Attribute-Based Access Control (ABAC)

ABAC builds on RBAC by factoring in object attributes (e.g., ownership, status). Instead of merely checking a user’s role, ABAC evaluates whether conditions tied to the user, action, and resource are met.

Key Components:

  1. Subject: The user requesting the action.
  2. Action: The intended operation (e.g., “delete,” “view”).
  3. Resource: The object being acted on (e.g., “comment”).
  4. Context: Additional factors (e.g., time, location, organization).

Example ABAC Logic:

function canDeleteComment(user, comment) {
    if (user.role === 'admin') return true;
    if (user.role === 'moderator') return true;
    if (user.id === comment.authorId && comment.status !== 'published') return true;
    return false;
}

Benefits of ABAC:

  • Granularity: Tailor permissions to specific scenarios.
  • Flexibility: Accommodates complex conditions effortlessly.

Design Hybrid Systems for Multi-Tenancy

For multi-tenant applications like Slack or Google Drive, combining RBAC and ABAC ensures scalability and customization. Users may belong to multiple organizations or need distinct permissions for specific resources.

Hybrid Data Model:

  1. Users Table: Stores user data.
  2. Roles Table: Defines available roles.
  3. Permissions Table: Links roles to actions.
  4. Organizations Table: (Optional) Tracks multi-tenant associations.

Example: Multi-Role Setup

const userRoles = [
    { userId: 1, role: 'admin', organizationId: 101 },
    { userId: 1, role: 'viewer', resourceId: 202 },
];

function getUserRoles(userId, context) {
    return userRoles.filter(role => role.userId === userId && matchesContext(role, context));
}

This model supports scenarios where users have varying roles across organizations or resources, e.g., being an “admin” for one team and a “viewer” for another.


Advanced Example: Google Drive-Style Permissions

Applications with shared resources — like files or folders — require an additional layer of granularity.

Table Example:

User ID Role Resource ID Resource Type
1 Owner 301 File
2 Editor 301 File
3 Viewer 302 Folder

Logic for Resource Sharing:

function canAccessResource(user, resourceId, action) {
    const permissions = getUserPermissions(user.id, resourceId);
    return permissions.some(permission => permission.actions.includes(action));
}

By consolidating resource-specific roles into a flexible schema, you simplify the logic while retaining the ability to scale.

This post empowers you to build scalable permission systems using RBAC, ABAC, and hybrid models. By choosing the right approach, you’ll ensure your application is future-proof and easy to maintain.

Recent Articles

Our work

We've worked with customers of every size: from startup to enterprise, and everything in between.
Here are a few of the exciting projects we've been working on recently.

See Our Work
*