RBAC and ABAC: The Art of Doing Authorization Right
Introduction
Most software projects start small and simple.
When authorization becomes necessary, many developers reach for a quick fix — something like if user.isAdmin() — and move on.
But as the project grows, these scattered checks multiply, logic overlaps, and the boundaries of what’s actually secure begin to blur.
At some point, tracking who can access what becomes a problem in itself.
In modern applications, security isn’t just about answering the question “Who is logged in?”
The real challenge is answering “What is this user allowed to do?” — accurately, consistently, and in a scalable way.
That’s where two foundational models come in:
RBAC (Role-Based Access Control) and ABAC (Attribute-Based Access Control).
These models replace ad-hoc developer logic with a structured framework built on roles, attributes, and policies.
Instead of hard-coded conditions buried across your codebase, authorization becomes centralized, auditable, and predictable.
RBAC and ABAC are the building blocks of sustainable security — especially in systems with multiple users, APIs, and microservices.
What Is RBAC (Role-Based Access Control)?
RBAC ties permissions to roles instead of individual users.
Users are assigned roles, and roles define which actions are allowed.
Example:
- Role: Admin → “Create user,” “Delete record”
- Role: Editor → “Edit data,” “Publish content”
- Role: Viewer → “Read only”
Instead of defining permissions per user, you simply assign roles.
Advantages:
- Simple and easy to reason about
- Mirrors organizational structure (e.g., department → role)
- Centralized policy management
What Is ABAC (Attribute-Based Access Control)?
ABAC takes authorization one step further.
It considers not only roles, but also attributes of users, resources, and the environment.
Example:
- User:
role=editor,department=HR - Resource:
type=document,owner=HR - Rule: “Only users from the HR department can access HR documents.”
In ABAC, access decisions depend on who, what, and under what conditions.
Advantages:
- Fine-grained control
- Dynamic rules (based on time, IP, or context)
- Perfect fit for microservices and API-driven systems

RBAC vs ABAC
| Feature | RBAC | ABAC |
|---|---|---|
| Basis | Roles | Attributes |
| Flexibility | Medium | High |
| Complexity | Low | Medium–High |
| Use Case | Fixed roles, clear hierarchy | Dynamic, context-aware systems |
| Example | “Admins can view all data” | “Users can view data only from their own department” |
RBAC Example in Go
Here’s a minimal RBAC implementation in Go: users are linked to roles, roles define permissions, and the Can() function checks access.
package main
import (
"fmt"
)
type Role string
const (
Admin Role = "admin"
Editor Role = "editor"
Viewer Role = "viewer"
)
type Permission string
const (
Create Permission = "create"
Read Permission = "read"
Update Permission = "update"
Delete Permission = "delete"
)
type RBAC struct {
rolePermissions map[Role][]Permission
}
func NewRBAC() *RBAC {
return &RBAC{
rolePermissions: map[Role][]Permission{
Admin: {Create, Read, Update, Delete},
Editor: {Create, Read, Update},
Viewer: {Read},
},
}
}
func (r *RBAC) Can(role Role, perm Permission) bool {
for _, p := range r.rolePermissions[role] {
if p == perm {
return true
}
}
return false
}
func main() {
rbac := NewRBAC()
fmt.Println("Admin delete?", rbac.Can(Admin, Delete)) // true
fmt.Println("Editor delete?", rbac.Can(Editor, Delete)) // false
}
⚠️ Warning: This ABAC code is for demonstration only.⚠️
If you deploy this version to production, the only thing you’ll control is a panic: unauthorized access. 😎
ABAC Example in Go
This ABAC example demonstrates a simple rule engine where access depends on user and resource attributes.
package main
import "fmt"
type AttributeMap map[string]string
type Policy struct {
Name string
Condition func(user, resource AttributeMap) bool
}
func main() {
// Define policies
policies := []Policy{
{
Name: "Department Access",
Condition: func(user, resource AttributeMap) bool {
return user["department"] == resource["owner"]
},
},
{
Name: "Time Restriction",
Condition: func(user, resource AttributeMap) bool {
return user["time"] == "working-hours"
},
},
}
// Example data
user := AttributeMap{"role": "editor", "department": "HR", "time": "working-hours"}
document := AttributeMap{"type": "document", "owner": "HR"}
// Evaluate policies
granted := true
for _, policy := range policies {
if !policy.Condition(user, document) {
granted = false
break
}
}
fmt.Println("Access granted?", granted)
}
⚠️ Warning: This ABAC code is for demonstration only.⚠️
If you deploy this version to production, the only thing you’ll control is a panic: unauthorized access. 😎
Why You Shouldn’t “Roll Your Own” Authorization System
- Easier maintenance: Avoid hundreds of scattered
ifconditions. - Centralized logic: Policies live in one place — not across your codebase.
- Better security: Fewer human errors, fewer blind spots.
- Compliance-friendly: Easier alignment with ISO, PCI-DSS, GDPR, etc.
- Testable: Policy engines can be verified independently.
Authorization is the backbone of software security.
Writing a custom system may feel faster at first, but it becomes a long-term liability.
RBAC is ideal for simple, role-based setups.
ABAC shines in dynamic, context-driven environments.
In many real-world systems, the best solution is a hybrid of both:
“A role-based core, enhanced by attribute-based rules.”
Final Thought:
Keep your code simple, your policies centralized, and never spread authorization logic across your app.
Because one day, your custom RBAC system might start authorizing itself. 😅