How Modern Apps Handle Roles and Permissions
Your user can view a document. But can they share it? Can they share it with anyone, or only within their organization? Can they revoke access they granted? These questions expose why traditional role-based access control breaks down in modern applications.
Static roles like “admin,” “editor,” and “viewer” worked when applications were simpler. Today’s collaborative, multi-tenant SaaS products need something more flexible. This article explains how modern authorization patterns solve these challenges and why the industry has moved beyond roles-only thinking toward fine-grained authorization.
Key Takeaways
- Traditional RBAC struggles with dynamic, relationship-based permissions and leads to role explosion in complex applications
- ReBAC determines access through entity relationships, making sharing and collaboration first-class concepts
- ABAC evaluates access based on user, resource, and contextual attributes for context-dependent rules
- Most production systems combine RBAC for broad categories with ReBAC for resource-level permissions
- Policy-as-code approaches make authorization logic testable, auditable, and version-controlled
Why Traditional RBAC Falls Short
Role-Based Access Control assigns permissions through roles. A user gets the “editor” role, which grants a predefined set of permissions. Simple and intuitive.
The problem emerges as applications grow. Consider a project management tool where users need different access levels per project, per workspace, and per document type. You start creating roles like “project-A-editor” and “workspace-B-admin.” This role explosion makes the system unmanageable.
RBAC also struggles with relationship-based questions. “Can this user edit this document?” depends on whether they own it, whether someone shared it with them, or whether they belong to a team that has access. Static roles can’t express these conditions elegantly.
Modern Authorization Patterns
Relationship-Based Access Control (ReBAC)
ReBAC determines access through relationships between entities. Instead of asking “does this user have the editor role?”, you ask “does this user have an editor relationship to this document?”
Google’s Zanzibar system pioneered this approach at scale. When you share a Google Doc, you’re creating a relationship. The system then traverses these relationships to answer permission questions. Tools like OpenFGA, SpiceDB and Permify implement Zanzibar-inspired models for applications of any size.
ReBAC handles collaborative scenarios naturally. Sharing, team membership, and organizational hierarchies become first-class concepts rather than awkward role workarounds.
Attribute-Based Access Control (ABAC)
ABAC evaluates access based on attributes of users, resources, and context. A policy might state: “users can access documents if their department matches the document’s department and the request occurs during business hours.”
This model excels at context-dependent rules. Healthcare applications use ABAC to enforce HIPAA requirements—access depends on the patient-provider relationship, the data sensitivity, and the access context.
RBAC vs ReBAC: When to Use Each
RBAC remains appropriate for applications with clear, static permission boundaries. Internal tools with well-defined user types benefit from its simplicity.
ReBAC fits better when permissions depend on resource ownership, sharing, or organizational structure. Any application with “share” functionality or multi-tenancy should consider ReBAC.
Most production systems combine both. RBAC handles broad categories (admin vs. regular user), while ReBAC manages resource-level permissions.
Discover how at OpenReplay.com.
Architectural Patterns for Access Control in Web Apps
Separating Authentication from Authorization
Authentication answers “who is this user?” Authorization answers “what can they do?” Modern architectures treat these as separate concerns with dedicated services for each.
Your identity provider handles authentication. A separate authorization service—whether built in-house or managed—handles permission decisions. This separation allows each system to evolve independently.
Policy-as-Code
Modern authorization patterns favor expressing rules as code rather than database configurations. Policy-as-code means your authorization logic lives in version control, gets reviewed in pull requests, and deploys through your standard pipeline.
Open Policy Agent (OPA) uses Rego for policy definitions. Cedar, developed by AWS, provides another policy language. These tools make authorization logic testable and auditable.
Centralized Policy, Distributed Enforcement
The pattern that scales: define policies centrally, enforce them at each service. Your policy engine maintains the rules. Each microservice queries the engine for permission decisions, often caching results for performance.
This architecture keeps authorization consistent across services while avoiding a single point of failure.
Frontend Implications
The backend remains the source of truth for all permission decisions. Never trust the frontend for security.
That said, frontends need permission information for good UX. Feature gating—hiding buttons users can’t click, disabling forms they can’t submit—requires knowing permissions client-side. The pattern is straightforward: use permission checks (for example, “can this user perform this action?”) for UI decisions, but always validate on the server.
Libraries like CASL help manage client-side permission logic while keeping the actual enforcement server-side.
Conclusion
Fine-grained authorization isn’t experimental—it’s how modern applications work. Google, GitHub, and Notion all use relationship-based models. Policy engines power authorization at companies of every size.
Start by identifying where your current RBAC struggles. If you’re creating roles for every permission combination, or if sharing and collaboration feel bolted-on, modern authorization patterns will simplify your system while making it more capable.
The shift from “what role does this user have?” to “what relationship does this user have to this resource?” changes how you think about permissions. It’s a better mental model for how users actually interact with modern applications.
FAQs
RBAC assigns permissions through static roles like admin or editor. ReBAC determines access through relationships between users and resources. While RBAC asks does this user have the editor role, ReBAC asks does this user have an editor relationship to this specific document. ReBAC handles dynamic sharing and collaboration more naturally.
Yes, most production systems combine both approaches. RBAC typically handles broad permission categories such as distinguishing admins from regular users. ReBAC manages resource-level permissions like document sharing and team access. This hybrid approach gives you simplicity for global permissions and flexibility for resource-specific access control.
Authentication identifies who the user is while authorization determines what they can do. Separating these concerns allows each system to evolve independently. Your identity provider handles login and identity verification. A dedicated authorization service manages permission decisions. This separation improves maintainability and lets you swap or upgrade either component without affecting the other.
Always enforce permissions on the backend. The server is the source of truth for all access control decisions. However, frontends need permission data for good user experience such as hiding unavailable buttons or disabling restricted forms. Fetch effective permissions on page load for UI decisions but validate every action server-side.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.