Back to Blog
Security
2026-02-03
5 min read

Authorization Patterns: RBAC, ABAC, and Beyond

A

Abhay Vachhani

Developer

Once a user is logged in, the next challenge is deciding what they are allowed to do. Is this person an Admin? A Manager? Can they edit this specific document, or just view it? This is Authorization. the art of granular permission management. In small apps, a simple is_admin flag might work, but in enterprise systems, you need robust patterns to stay secure and maintainable.

1. Role-Based Access Control (RBAC): The Foundation

RBAC is the most common authorization pattern. You group permissions into "Roles" (Admin, User, Editor) and then assign those roles to users. It’s easy to audit: "Who has the Editor role?" is a simple query.

The Implementation: Usually involves a join table between Users and Roles, and a middleware in Express to verify membership.

// Simple RBAC Middleware
const authorize = (...roles) => {
    return (req, res, next) => {
        if (!roles.includes(req.user.role)) {
            return res.status(403).json({ message: 'Forbidden' });
        }
        next();
    };
};

2. Attribute-Based Access Control (ABAC): Fine-Grained Logic

RBAC fails at scale. If you need a rule like "A doctor can only view a patient record if they are the assigned doctor AND the medical center is currently open," you need ABAC.

ABAC evaluates a set of **attributes** to make a decision:

  • Subject: The user making the request (e.g., clearance level).
  • Resource: The object being accessed (e.g., record status).
  • Action: What is being done (e.g., read, write).
  • Environment: Contextual data (e.g., time of day, IP address).

3. Modern Policy Engines: CASL and OPA

Moving authorization logic out of your route handlers is critical. CASL is a favorite for Node.js. It allows you to define rules in a declarative way that can be shared between Backend and Frontend.

For even larger systems, Open Policy Agent (OPA) allows you to move authorization into a separate service entirely, using a logic language called **Rego**. This is perfect for polyglot microservices where you want a single source of truth for permissions across Go, Node.js, and Java services.

4. Relationship-Based Access Control (ReBAC): The Zanzibar Pattern

Pioneered by Google (Zanzibar) and implemented by tools like **Ory Keto** or **Warrant**, ReBAC models permissions as a graph of relationships. Instead of asking "Does User A have role Admin?", you ask "Is User A an Owner of Folder B which contains Document C?". This allows for extremely complex, nested permission structures (like Google Drive) that scale to billions of objects.

5. Audit Logging and Traceability

In a regulated environment, "Allow/Deny" is not enough. You must also record **Why** a decision was made. Every authorization check should ideally be logged with the context (User ID, Resource ID, Rule matched). This is critical for security forensics and compliance audits (SOX, HIPAA).

Conclusion

Authorization is a journey from simple flags to complex graphs. For most apps, starting with RBAC + CASL provides the best balance of speed and future-proofing. As you grow into an enterprise-scale system, looking at ABAC for context and ReBAC for relationships becomes the clear path forward. Remember: the goal of authorization is to get out of the way of your users while ensuring that the wrong person never sees a single byte of data they're not entitled to.

FAQs

When should I switch from RBAC to ABAC?

Switch to ABAC when your permission logic starts depending on context, such as ownership, time, or location, rather than just a fixed job title.

Can I share CASL abilities with my React frontend?

Yes! This is one of CASL's biggest strengths. You can send the serialized rules to the client and use the same logic to hide UI elements that the user doesn't have permission to use.

What is the "Confused Deputy" problem?

It is a security vulnerability where a service is tricked into using its higher privileges to perform an action on behalf of an unauthorized user. Proper authorization checks must always happen at the boundary of every service.

How do I handle "Delegated" authorization?

Use OAuth2 Scopes. This allows a user to grant a third-party application limited access to their resources without sharing their primary credentials (e.g., "Allow this app to READ my profile, but not DELETE it").