3 min read

RBAC and ABAC: The Art of Doing Authorization Right

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.
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

  1. Easier maintenance: Avoid hundreds of scattered if conditions.
  2. Centralized logic: Policies live in one place — not across your codebase.
  3. Better security: Fewer human errors, fewer blind spots.
  4. Compliance-friendly: Easier alignment with ISO, PCI-DSS, GDPR, etc.
  5. 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. 😅